Giter Club home page Giter Club logo

blog's People

Contributors

guocover avatar itsjw avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

itsjw

blog's Issues

前端如何呼风唤雨

前言

创世纪第一章

首卷原文

起初我创造了canvas 。

我说,要有雨,就有了雨;

我说,要有雪,就有了雪。

而对于前端来说,canvas即是天地

在canvas这个天地上,前端可以呼风唤雨,无所不能。

------------------------------------华丽的分割线-------------------------------------------------

文章起因

其实就是最近在做一个需求,需要有下雨下雪的动画特效,
故在这里做了一个drop的组件,来展现这种canvas常见的下落物体的效果。那么,=。= ,就让我们先看看效果吧。

[github地址] 之后贴出来哈。。。。

效果展示

调用代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        #canvas{
            width:100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script src="canvasDrop.js"></script>
    <script>
        canvasDrop.init({
            type: "rain",  // drop类型,有rain or snow
            speed : [0.4,2.5], //速度范围
            size_range: [0.5,1.5],//大小半径范围
            hasBounce: true, //是否有反弹效果or false,
            wind_direction: -105 //角度
            hasGravity: true //是否有重力考虑
        });
    </script>
</body>
</html>

下雨 下雪


看起来效果还是不错的,相对于使用创建dom元素来制作多物体位移动画,
使用canvas会更加容易快捷,以及性能会更好

源码讲解

好了,接下来讲解一下简单的实现原理
首先,先定义一些我们会用到的全局变量,如风向角度,几率,对象数据等

定义全局变量

//定义两个对象数据
//分别是drops下落物体对象
//和反弹物体bounces对象
var drops = [], bounces = [];
//这里设定重力加速度为0.2/一帧
var gravity = 0.2;


var speed_x_x, //横向加速度
	  speed_x_y, //纵向加速度
	  wind_anger;  //风向
//画布的像素宽高
var canvasWidth,
	canvasHeight;
//创建drop的几率
var drop_chance;
//配置对象
var OPTS;
//判断是否有requestAnimationFrame方法,如果有则使用,没有则大约一秒30帧
window.requestAnimFrame =
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(callback) {
        window.setTimeout(callback, 1000 / 30);
    };

定义核心对象

接下来我们需要定义几个重要的对象
该组织所需定义的对象也比较少,总共才三个
在整个drop组件**定义了`三个核心对象,分别是如下:

  • ** Vector 速度对象**,带有横向x,和纵向y的速度大小 单位为:V = 位移像素/帧

对于Vector对象的理解也十分简单粗暴,就是记录下落对象drop的速度/V

var Vector = function(x, y) {
	//私有属性  横向速度x ,纵向速度y
	this.x = x || 0;
	this.y = y || 0;
};
//公有方法- add : 速度改变函数,根据参数对速度进行增加
//由于业务需求,考虑的都是下落加速的情况,故没有减速的,后期可拓展
/*
* @param v  object || string  
*/
Vector.prototype.add = function(v) {
	if (v.x != null && v.y != null) {
		this.x += v.x;
		this.y += v.y;
	} else {
		this.x += v;
		this.y += v;
	}
	return this;
};
//公有方法- copy : 复制一个vector,来用作保存之前速度节点的记录
Vector.prototype.copy = function() {
	//返回一个同等速度属性的Vector实例
	return new Vector(this.x, this.y);
};
  • Drop 下落物体对象, 即上面效果中的雨滴和雪, 在后面你也可自己拓展为陨石或者炮弹

对于Drop对象其基本定义如下

//构造函数
var Drop = function() {
	/* .... */
};
//公有方法-update 
Drop.prototype.update = function() {
	/* .... */
};
//公有方法-draw
Drop.prototype.draw = function() {
	/* .... */
};

看了上面的三个方法,是否都猜到他们的作用呢,接下来让我们了解这三个方法做了些什么

构造函数

构造函数主要负责定义drop对象的初始信息,如速度,初始坐标,大小,加速度等

//构造函数 Drop

var Drop = function() {
    //随机设置drop的初始坐标 
    //首先随机选择下落对象是从从哪一边
	var randomEdge = Math.random()*2;
	if(randomEdge > 1){
		this.pos = new Vector(50 + Math.random() * canvas.width, -80);
	}else{
		this.pos = new Vector(canvas.width, Math.random() * canvas.height);
	}

	//设置下落元素的大小
     //通过调用的OPTS函数的半径范围进行随机取值
	this.radius = (OPTS.size_range[0] + Math.random() * OPTS.size_range[1]) *DPR;
    
	//获得drop初始速度
    //通过调用的OPTS函数的速度范围进行随机取值
	this.speed = (OPTS.speed[0] + Math.random() * OPTS.speed[1]) *DPR;
   
	this.prev = this.pos;
	//将角度乘以 0.017453293 (2PI/360)即可转换为弧度。
	var eachAnger =  0.017453293; 
    //获得风向的角度
	wind_anger = OPTS.wind_direction * eachAnger;
    //获得横向加速度 
	speed_x =  this.speed * Math.cos(wind_anger);
	//获得纵向加速度
	speed_y = - this.speed * Math.sin(wind_anger);
    
    //绑定一个速度实例
	this.vel = new Vector(wind_x, wind_y);
  
};

Drop对象的update方法

update方法负责,每一帧drop实例的属性的改变
如位移的改变

Drop.prototype.update = function() {

  	this.prev = this.pos.copy();
	//如果是有重力的情况,则纵向速度进行增加
  	if (OPTS.hasGravity) {
  		this.vel.y += gravity;
  	}
  //
   this.pos.add(this.vel);
};

Drop对象的draw方法

draw方法负责,每一帧drop实例的绘画

Drop.prototype.draw = function() {

  ctx.beginPath();
  ctx.moveTo(this.pos.x, this.pos.y);
//目前只分为两种情况,一种是rain  即贝塞尔曲线
  if(OPTS.type =="rain"){
	   ctx.moveTo(this.prev.x, this.prev.y);
	   var ax = Math.abs(this.radius * Math.cos(wind_anger));
	   var ay = Math.abs(this.radius * Math.sin(wind_anger));
	   ctx.bezierCurveTo(this.pos.x + ax, this.pos.y + ay, this.prev.x + ax , this.prev.y + ay, this.pos.x, this.pos.y);
	   ctx.stroke();
       
  //另一种是snow--即圆形     
  }else{
  	   ctx.moveTo(this.pos.x, this.pos.y);
  	   ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI*2);
  	   ctx.fill();
  }
};
  • bounce 下落落地反弹对象, 即上面雨水反弹的水滴, 你也可后期拓展为反弹的碎石片或者烟尘

定义的十分简单,这里就不做详细说明

var Bounce = function(x, y) {

  var dist = Math.random() * 7;
  var angle = Math.PI + Math.random() * Math.PI;

  this.pos = new Vector(x, y);
  this.radius =  0.2+ Math.random()*0.8;
  this.vel = new Vector(
    Math.cos(angle) * dist,
    Math.sin(angle) * dist
    );
};

Bounce.prototype.update = function() {

  this.vel.y += gravity;

  this.vel.x *= 0.95;
  this.vel.y *= 0.95;

  this.pos.add(this.vel);
};

Bounce.prototype.draw = function() {

  ctx.beginPath();
  ctx.arc(this.pos.x, this.pos.y, this.radius*DPR, 0, Math.PI * 2);
  ctx.fill();

};

对外接口

update

即相当于整个canvas动画的开始函数

function update() {

	var d = new Date;
	//清理画图
	ctx.clearRect(0, 0, canvas.width, canvas.height);

	var i = drops.length;
	while (i--) {

		var drop = drops[i];

		drop.update();
		//如果drop实例下降到底部,则需要在drops数组中清楚该实例对象
		if (drop.pos.y >= canvas.height) {
			//如果需要回弹,则在bouncess数组中加入bounce实例
			if(OPTS.hasBounce){
				var n = Math.round(4 + Math.random() * 4);
				while (n--)
				bounces.push(new Bounce(drop.pos.x, canvas.height));
			}
           //如果drop实例下降到底部,则需要在drops数组中清楚该实例对象
			drops.splice(i, 1);
		}
	
		drop.draw();
    }
    //如果需要回弹
    if(OPTS.hasBounce){
		var i = bounces.length;
		while (i--) {
			var bounce = bounces[i];
			bounce.update();
			bounce.draw();
			if (bounce.pos.y > canvas.height) bounces.splice(i, 1);
		}
    }
	//每次产生的数量
	if(drops.length < OPTS.maxNum){
		if (Math.random() < drop_chance) {
			var i = 0,
				  len = OPTS.numLevel;
			for(; i<len; i++){
				drops.push(new Drop());
			}
		}

	}
	//不断循环update
	requestAnimFrame(update);
}

init

init接口,初始化整个canvas画布的一切基础属性
如获得屏幕的像素比,和设置画布的像素大小,和样式的设置

function init(opts) {
	OPTS = opts;

	canvas = document.getElementById(opts.id);
	ctx = canvas.getContext("2d");
    
	////兼容高清屏幕,canvas画布像素也要相应改变
	DPR = window.devicePixelRatio;
    
	//canvas画板像素大小, 需兼容高清屏幕,故画板canvas长宽应该乘于DPR
	canvasWidth = canvas.clientWidth * DPR;
	canvasHeight =canvas.clientHeight * DPR;
    
	//设置画板宽高
	canvas.width = canvasWidth;
	canvas.height = canvasHeight;

	drop_chance = 0.4;
	//设置样式
	setStyle();
}

function setStyle(){
	if(OPTS.type =="rain"){
		ctx.lineWidth = 1 * DPR;
		ctx.strokeStyle = 'rgba(223,223,223,0.6)';
		ctx.fillStyle = 'rgba(223,223,223,0.6)';
		
	}else{
		ctx.lineWidth = 2 * DPR;
		ctx.strokeStyle = 'rgba(254,254,254,0.8)';
		ctx.fillStyle = 'rgba(254,254,254,0.8)';
	}
}

结束语

好了,一个简单的drop组件已经完成了,当然其存在着许多地方不够完善,经过本次drop组件的编写,对于canvas的动画实现,我相信在H5的场景中拥有着许多可发掘的地方。

最后说下不足的地方和后期的工作哈:

  • 0、该组件目前对外接口不够多,可调节的范围并不是很多,抽象不是很彻底
  • 1、 setStyle 设置 基本样式
  • 2、 Drop 和Bounce 对象的 update 和 draw 方法的自定义,让用户可以设立更多下落的
    速度和大小改变的形式和样式效果
  • 3、 应增加对动画的pause,加速和减速等操作的接口

测试用例的那一回事

前言

最近,团队对测试用例十分的注重,因此,下面是我对测试用例的一些解析。

首先,我们需要知道:为什么需要测试用例?

理由很简单,就是为了在测试用例的辅助下,编写出高质量,可维护代码。


问题

正如因为地震的爆发,才会有地震仪的诞生。
测试用例的诞生,也必然有其需要解决的问题:

当我们在开发,我们往往会有以下的问题:

需求和开发脱节

当一份需求来了, 开发人员往往不能百分百的理解需求的内容(抛弃产品自己变更需求的可能性。。),这往往会让开发人员开发出的功能会有跟需求有所差别,这会带来额外的工作量

开发和测试脱节

什么是开发和测试脱节,说的是,当开发人员按照自己的想法开发完了一个需求。然后测试人员也按照自己的想法去测试这个需求,然后由于双方的分歧,导致测试认为开发有bug,开发认为测试是sb.

那么如何解决上面的问题呢?

答案就是 选择一种软件敏捷开发模式


敏捷开发模式

目前比较流行的开发模式有两种: TDD 和 BDD

###TDD (Test Driven Development 测试驱动开发)

  • 测试来驱动开发
  • 其重点偏向开发
  • 测试用例是在约束开发者,使开发者的目标明确,设计出满足需求的系统

BDD (Behaviour Driven Development 行为驱动开发)

  • 基于TDD发展,保持测试先行的理念
  • 其重点偏向设计
  • 在测试代码中用一种自然通用语言的方式把系统的行为描述出来
  • 将系统的设计和测试用例结合起来,进而驱动开发工作

两种方式各有其特点,我们通常选择的是BDD的方式


测试工具

为了,方便我们编写测试用例,我们需要使用一些可靠工具,以下是我认为比较好的前端测试用例工具。

Mocha 摩卡

Mocha(发音"摩卡")诞生于2011年,是现在最流行的JavaScript测试框架之一,在浏览器和Node环境都可以使用。
通过Mocha, 我们可以安装基于mocha的规范,轻松的编写测试用例和管理测试用例。

Mocha测试脚本如何编写

对于mocha, 一个测试用例必定包含 describeit,来实现一个测试用例的具体模版
describe块 称为"测试套件"(test suite),表示一组相关的测试。
it块 称为"测试用例"(test case),表示一个单独的测试,是测试的最小单位

以上的是同步情况的测试用例

若我们需要测试异步代码时,只需要在每个it的回调中,增加done的参数,具体如下

Should JS 苏德 断言库

所有的测试用例(it块)都应该含有断言。
断言功能由断言库来实现,Mocha本身不带断言库,所以必须先引入断言库。
因此,我们在上面的测试用例添加断言:如下

Nock 诺克 HTTP响应mock工具

有时,我们可能需要模拟HTTP请求的响应数据
是否有工具可以简化呢?那就是Nock啦,
Nock使用起来十分方便,API都十分简单名利

 var should = require('should');
 var nock = require('nock');
 var teacheModel = require('teacherModel');
 //定义模拟的http请求的响应结果
 var cgiData = {
        retcode:0,
        result: {
           num: 10,
        }
    };
    //测试方法updateTeacherData
    describe('测试Module.updateTeacherData()', function() {  
        it('请求接口,能够正确处理数据', function() {
          //这里定义,get请求XXX.qq.com域名下的/cgi-bin/teacher/get_about的数据能返回200
          //并且返回结果是cgiData
            nock('http://xxx.qq.com')
                .get('/cgi-bin/teacher/get_about')
                .reply(200, cgiData);

            testModule.getData({}, req, function(data){
                data.retcode.should.equal(0, 'teacher数据拿到, 不能正确处理');
            })
        });
   
    });

通过nock,直接模拟请求结果,这样我们就可以不考虑cgi的状态,而专注于model的逻辑测试

Istanbul 伊斯坦布尔 代码覆盖率检验工具

测试用例写好了, 怎么验证是否写得好?
Istanbul是可以给出测试用例的代码覆盖率检验的工具

如下面我们使用istanbul,可以看到我们的util.test.js的覆盖率情况

然后,如果想知道具体覆盖率情况,可以通过打开生成的报文去查看,如下面可以知道,哪些部分测试用例没有覆盖到

mochawesome

万事俱备?NO NO NO!
我们还需要更好的视觉体验
通过使用mochawesome工具,在当命令行运行 mocha 是增加 --reporter mochawesome 参数,将测试用例运行情况转成更为直观的测试报文,如下:


补充

懂得如何编写测试用例,但仍需要有一套比较明确的编写规范和,编写教程,才能让项目的测试用例生生不息,持之以恒带来功效、

滚动,你真的懂了吗

前言

在业务中,页面滚动的场景十分常见,因此对于滚动的充分了解,可以让我们提高开发的效率!

滚动的几种场景

  • 只有window窗体滚动
  • 内滚动布局
  • 窗体滚动+DIV内滚动

这时候,台下观众会问,什么是内滚动布局,什么是window滚动呢?
接下来就让我们来了解下哈!

只有window窗体滚动

即页面只含有浏览器窗体默认的滚动条,窗体滚动条随页面内容而不断增长。

如手Q吃喝玩乐的站点首页, 在android机上就是使用window滚动

内滚动布局

什么是内滚动布局呢?
个人认为,内滚动布局就是主滚动条是在页面内部,而不是浏览器窗体上的布局。
故内滚动布局是相对传统的window窗体滚动而言的。

(具体为什么ios上和android上会使用不同的滚动方式,可以去了解下=。=)

内滚动布局什么时候会使用了?

  • ios 页面顶部带有fixed输入框(解决软键盘弹出导致页面错位的问题)

例如,手Q吃喝玩乐的站点首页, 在ios机上便是内滚动布局

  • 桌面软件或者客户端,如群活动

  • 管理系统也有经常使用

窗体滚动+DIV内滚动

这种场景就是,两者都会出现,故计算滚动时最为复杂、

滚动计算基础知识

由于不同浏览器其窗体滚动条的属性获取方式有所差异,故考虑兼容性,我们假设使用了场景是移动到,并且使用了zepto的库

首先,我们想要更好的操作控制条,需了解两个地方

  • 滚动条属性
  • 滚动条调用方法
var $scrollTarget = $(".ui-page");

//若为控制window滚动条
var currenY = $(document.body).scrollTop() //当前window纵向滚动的位置
var currenX = $(document.body).scrollLeft() //当前window横向滚动的位置
var Y = 想滚动到的垂直位置;
var X = 想滚动到的水平位置;
$(window).scrollTop(Y)
$(window).scrollLeft(X)

//若为页面内节点的滚动条
var currenY =$scrollTarget.scrollTop() //当前scrollTarget纵向滚动的垂直位置
var currenX = $scrollTarget.scrollLeft() //当前scrollTarget横向滚动条的位置
$scrollTarget.scrollTop(Y)
$scrollTarget.scrollLeft(X)

我们可以发现
在这里window滚动比较特殊

其获取滚动属性是用 document.body这个对象,而调用滚动条滚动方法是用window的对象

(不同浏览器其获取浏览器窗体滚动条的方式也存在着差异,大家可以去了解下)

接下来,我们了解下几个重要的属性值

//当前window可视内容区域宽高: 
window.innerWidth
window.innerHeight
//浏览器滚动条偏移值:
$(document.body).scrollTop();
//节点offset值
$("#div").offset().top;
$("#div").offset().left;
//节点的宽高
$("#div").height();
$("#div").width();
//节点的滚动条偏移值
$("#div").scrollTop();

现在我们知道如何调用滚动条到指定的位置和获取滚动条偏移值,那么我们来做一个需求把

假设是这个页面


需求描述 : 希望通过点击按钮,使绿色区域的item,能够定位到屏幕中间

这种需求很常见吧~

那么我们先分析下页面,
可以从页面中看出,这个是一个内滚动布局单页页面。

//相信我们js代码就是这样写的
var itemHeight = 每个item的高度
var itemIndex = 指定item的下标(1, 2,3 ...)
var rightPosY =  itemHeight * itemIndex //使绿色区域的item能滚动到列表可视区域的中间的偏移值;
$(button).on("click", function(){
	$(scrollDom).scrollTop(rightPosY);
});

那么大家就不满意了,举起双手抗议说 :“这个太简单了,如果每个列表item都是高度不一致,且高度未知的呢?

那么问题升级了,我们来考虑下这个问题,如下图分析图

为了使目标节点,移动到内滚动区域的中间线
我们最终需要知道当前目标节点距离中间线的偏移值,然后加上当前滚动区域的滚动条偏移值,便是我们需要滚动条滚动到的偏移值大小了。

即最终偏移值 = 当前目标节点距离中间线的偏移值 + 当前滚动区域的滚动条偏移值;

可知,当前滚动区域的滚动条偏移值即等于 $("#scrollDom").scrollTop();

那么distance 怎么获取呢?
我们可以从上面的分析图得出

var distance =  $("#targetDom").offset().top - $("#scrollDom").offset().top - $("#scrollDom").height/2;

完整代码

var distance =  $("#targetDom").offset().top - $("#scrollDom").offset().top - $("#scrollDom").height/2;
//算出当前节点在
var rightY  = $(scrollDom).scrollTop + distance;
var $(scrollDom).scrollTop(rightY);

只要弄明白了滚动涉及的属性和方法,在业务开发中,则能迅速得到想要的滚动效果。

短信中的链接为什么那么短?

前言

前段时间读了一些关于短网址的文章,加上刚好收到一个含有短网址的短信。一时兴起或者说是顺其自然就写了这篇文章哈。

什么是短 URL?

短网址(Short URL),顾名思义就是比较短的 URL 网络地址, 在如今 Web 2.0 的时代,短网址十分得流行,在业界已经有许多短网址生成的服务,使我们可以用各位简短的网址来替代原来十分冗长的网址。让分享的网页链接不会因为太长而引起用户反感,影响体验,使使用者更容易分享哈。

事实上,短网址,也就是短链接在我们生活中随处可见,如微博分享、外卖订单信息、或者如上面的快递短信,短信中就含有一条短网址 http://tb.cn/vvDezXw 。)

当我们打开短网址时,网页会直接跳转到你要缩短的网址,就如打开上面的短网址,其会通过重定向的方式如 302 跳转到天猫的一个页面网址(相对短网址来说,所对应的网址长的多)

短网址的意义

使用短链接有什么好处呢?事实上,它有下面几个好处:

  • 内容需要(缩短URL满足字数限制)
  • 简介美观(用户友好)
  • 统计需要(网页流量统计、点击统计等)

无可否认的是,在微博和手机短信提醒等限制字数(一条短信最多就一两百个字)的地方来使用短网址,不得不说是一个不错的方案。一条短信是有限制字数,如果分享链接过长,就无法展示更多消息内容了。因此我们可以发现,在许多短信中,其网址都是短网址 URL

我们上面天猫的一个短网址 http://tb.cn/vvDezXw 其是通过 302 的方式,即临时重定向的方式进行跳转的。目的就是为来能够在跳转前做一些短网址打开的次数统计,这些统计数据能够成为大数据分析的数据源,从而分析用户的生活习惯兴趣爱好。

短网址的原理

那么,短网址是如何生成的呢?短网址服务是如何将那么多的长网址对应到相应的短网址呢?这里简单说明下:

短网址通常结构如下:域名/短网址id

短网址 id 其通常由 26 个大写字母 + 26 小写字母 +10 个数字 即 62 种字符组成,随机生成 6 到 7 个,然后组成对应一个 短网址 id,并存入相应的数据存储服务中。

当短网址被访问的时候,短网址的解析服务,会根据 id 查询到对应页面从而实现相应的跳转。

如何保证短网址 id 不重复

事实上,假如短网址 id 为 6 位,那就是共有 2^62 个短网址。超过这个数目的网页可能性并不大。但在生成即发放短网址的时候,需要保证能够发送不重复的短网址 id。

为了保证不冲突和重复,大多数短网址服务都会采用自增的方式来分发 id,如第一个使用这个服务的人得到的短地址是 http://xxx/0 ,第11个是 http://xxx/a 等依次生成。

对于大多数小型的短网址服务,直接使用 mysql 的自增索引就可以保证不冲突,但这种方式不太适合大型的应用。因为每次操作都需要涉及数据库的增删的资源损耗。因此对于一些大型应用,我们可以通过一些分布式 key-value 系统做短网址的分发。同时不停的自增就可以来。

如何分布式生成不重复的短网址?

如果生成短网址的服务是分布式的(用户量很大,只有一台生成一台不够用,如天猫、新浪微博),那么每个服务节点要保持同步自增,而不起冲突。是怎么做的呢?

事实上我们可以这样做。加入我们要实现有 5 台分布的短网址服务,此时我们让:

  • 服务 1,从 1 开始发放,然后每次自增 5 即 1、6、11、16...
  • 服务 2,从 2 开始发放,然后每次自增 5 即 2、7、12、17...
  • 服务 3,从 3 开始发放,然后每次自增 5 即 3、8、13、18...
  • 服务 4,从 4 开始发放,然后每次自增 5 即 4、9、14、19...
  • 服务 5,从 5 开始发放,然后每次自增 5 即 5、10、15、20...

这样每个分布的服务都能够独立工作,从而互不干扰。从而实现分布式发放。

公共短网址服务

正如前面所说,市面上有许多短网址的服务商。如下所示:

1、google 短网址服务 goo.gl

google 的 goo.gl 每次同样的网址生成的短网址都是不一样的

2、新浪 短网址服务 /sina.lt

而新浪的则是一定时间内,同样的网址生成的短网址都是一样的。且支持短网址后缀选择。

参考文章

WebAR实现简单版pokemon Go

背景:

最近AR的话题很火,如前段时间pokemon Go游戏,和支付宝的AR红包,加上最近看到了一些关于前端运用webRTC技术实现WebAR的文章,这边就尝试结合下,实现一个简单版的pokeMon Go的游戏。由于有兼容性问题,目前demo只是跑在android的手Q中,具体效果如下:

元旦后提供demo链接

WebAR

WebAR说白了就是通过web端的技术能力去实现AR的效果!

我们知道,AR最基础要实现的功能其实就是实时视频效果,然而帮助我们实现这种实时视频效果的技术基础是WebRTC;

WebRTC是什么?

那么,WebRTC是什么? 对前端来说,我们可以通过HTML5的新特性WebRTC(网页实时通信,Web Real-Time Communication 一个支持网页浏览器进行实时语音对话或视频对话的API),通过WebRTC,可以通过网页呼起用户的摄像头,并且实时获取用户摄像头的图像数据的。

WebRTC API

WebRTC共分三个API。

  • getUserMedia getUserMedia主要用于获取视频和音频信息
  • RTCPeerConnection 用于浏览器之间的数据交换。
  • RTCDataChannel `用于浏览器之间的数据交换。``

这边目前我只使用到了getUserMedia

WebRTC兼容性

这边通过阅读相关文章了解到,目前兼容性情况如下:

由于苹果的安全机制问题,iOS设备任何浏览器都不支持getUserMedia()。
最终数据展示,Android设备下,有99.45%的设备在微信是支持getUserMedia()的,98.05%的设备在手Q是支持getUserMedia()的。而我们之前测试机型里面,本机浏览器、QQ浏览器对getUserMedia()都有不同程度的支持。
2015年底前,也就是chrome47版本前,chrome是支持http页面拉起摄像头的,出于安全问题考虑,chrome47版本后只支持https页面拉起摄像头。

实现步骤

目前我的demo的实现步骤如下:

  • 通过WebRTC的API来实现获取通过浏览器网页拉起摄像头操作
// 获取相应的浏览器内核的getUserMedia
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;  
// 调用getUserMedia方法
function getMedia() {  
    if (navigator.getUserMedia) {  
        navigator.getUserMedia({  
            'video': {  
                'optional': [{  
                    'sourceId': exArray[1] //0为前置摄像头,1为后置  
                }]  
            },  
            'audio':true  
        }, successFunc, errorFunc);    //success是获取成功的回调函数  
    }  
    else {  
        alert('Native device media streaming (getUserMedia) not supported in this browser.');  
    }  
}  
 // 获取摄像头源信息
 // 通常手机只有两个源,前置和后置
 MediaStreamTrack.getSources(function (sourceInfos) {  
      for (var i = 0; i != sourceInfos.length; ++i) {  
          var sourceInfo = sourceInfos[i];  
          //这里会遍历audio,video,所以要加以区分  
          if (sourceInfo.kind === 'video') {  
              // alert('sourceInfo'+ sourceInfo.id);
              exArray.push(sourceInfo.id);  
          }  
      }  
   	 // 调用摄像头  
      getMedia();
      // 定时展示小精灵
      showPet();
  });  
  • 获取摄像头的数据流

当成功呼起摄像头时,会触发success的回调,在回调中我们可以获取摄像头的数据流

// 获取相应浏览器的URL对象
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;  
function successFunc(stream) {  
     // 这里的stream为摄像头的实时数据
  }  
  • 将摄像头的数据流通过video标签作为载体呈现在页面上
// 获取相应浏览器的URL对象
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;  
function successFunc(stream) {  
      if (video.mozSrcObject !== undefined) {  
          //Firefox中,video.mozSrcObject最初为null,而不是未定义的,我们可以靠这个来检测Firefox的支持  
          video.mozSrcObject = stream;  
      }  
      else {  
          video.src = window.URL && window.URL.createObjectURL(stream) || stream;  
      }  
      // 音频  
      audio = new Audio();  
      audioType = getAudioType(audio);  
      if (audioType) {  
          audio.src = 'polaroid.' + audioType;  
          audio.play();  
      }  
  }  
  • 可以在video上叠加任何我们需要的内容和操作

  • 配合CSS3和JS为叠加的内容增加交互效果,营造出WebAR的感觉

最终效果

小结

虽然目前webAR还是不能取代AppAR,且通过web来实现AR还是有许多问题。但我相信通过时代和技术的并行发展,webAR的流行可能并不久远。

参考链接

http://blog.csdn.net/journey191/article/details/40744015
http://tgideas.qq.com/webplat/info/news_version3/804/7104/7106/m5723/201612/537832.shtml

浏览器进程、线程?傻傻分不清楚!

在生活中,浏览器和我们的工作和生活息息相关。做为前端开发,我们代码的应用场景往往是在浏览器上。浏览器对前端的重要性不可一日而语。那么我们对浏览器是否有比较清晰的了解呢?什么是多进程架构浏览器?为什么浏览器内核是多线程?Javascript 是单线程又是什么鬼?进程和线程是否分得清楚呢?

进程(process)和线程(thread)

进程和线程是操作系统的基本概念,许多人会有所了解,但不能较为清晰的分辨。
这里我们需要了解下面几个点。

CPU

CPU是计算机的核心,其负责承担计算机的计算任务。这里我们比喻为一个工厂

进程

学术上说,进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。我们这里将进程比喻为工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

线程

在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元。这里把线程比喻一个车间的工人,即一个车间可以允许由多个工人协同完成一个任务。

进程和线程的区别和关系

  • 进程是操作系统分配资源的最小单位,线程是程序执行的最小单位。
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
  • 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号)。
  • 调度和切换:线程上下文切换比进程上下文切换要快得多

多进程和多线程

  • 多进程:多进程指的是在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
  • 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

浏览器多进程架构

跟现在的很多多线程浏览器不一样,Chrome浏览器使用多个进程来隔离不同的网页。因此在Chrome中打开一个网页相当于起了一个进程

那么Chrome为什么要使用多进程架构?

在浏览器刚被设计出来的时候,那时的网页非常的简单,每个网页的资源占有率是非常低的,因此一个进程处理多个网页时可行的。然后在今天,大量网页变得日益复杂。把所有网页都放进一个进程的浏览器面临在健壮性,响应速度,安全性方面的挑战。因为如果浏览器中的一个tab网页崩溃的话,将会导致其他被打开的网页应用。另外相对于线程,进程之间是不共享资源和地址空间的,所以不会存在太多的安全问题,而由于多个线程共享着相同的地址空间和资源,所以会存在线程之间有可能会恶意修改或者获取非授权数据等复杂的安全问题。

在了解这个知识点线,我们需要先说明下什么是浏览器内核

浏览器内核

简单来说浏览器内核是通过取得页面内容、整理信息(应用CSS)、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。从上面我们可以知道,Chrome浏览器为每个tab页面单独启用进程,因此每个tab网页都有由其独立的渲染引擎实例。

浏览器内核是多线程

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript 引擎线程
  • 定时触发器线程
  • 事件触发线程
  • 异步http请求线程

GUI渲染线程

GUI渲染线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被”冻结”了.

Javascript 引擎线程

Javascript 引擎,也可以称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。Javascript 引擎线程理所当然是负责解析Javascript脚本,运行代码。

Javascript 是单线程的

Javascript 是单线程的, 那么为什么Javascript 要是单线程的?

这是因为Javascript这门脚本语言诞生的使命所致:JavaScript为处理页面中用户的交互,以及操作DOM树、CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。如果JavaScript是多线程的方式来操作这些UI DOM,则可能出现UI操作的冲突;
如果Javascript 是多线程的话,在多线程的交互下,处于UI中的DOM节点就可能成为一个临界资源,假设存在两个线程同时操作一个DOM,一个负责修改一个负责删除,那么这个时候就需要浏览器来裁决如何生效哪个线程的执行结果。当然我们可以通过锁来解决上面的问题。但为了避免因为引入了锁而带来更大的复杂性,Javascript在最初就选择了单线程执行。

GUI 渲染线程 与 JavaScript 引擎线程互斥!

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JavaScript线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JavaScript引擎为互斥的关系,当JavaScript引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到引擎线程空闲时立即被执行。

JS阻塞页面加载

从上面我们可以推理出,由于GUI渲染线程与JavaScript执行线程是互斥的关系,当浏览器在执行JavaScript程序的时候,GUI渲染线程会被保存在一个队列中,直到JS程序执行完成,才会接着执行。因此如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

定时触发器线程

浏览器定时计数器并不是由JavaScript引擎计数的, 因为JavaScript引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。

事件触发线程

当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

异步http请求线程

在XMLHttpRequest在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理。

相关阅读

进程与线程的一个简单解释

Object.defineProperty 是什么

前言

Object.defineProperty ,顾名思义,为对象定义属性。在js中我们可以通过下面这几种方法定义属性

// (1) define someOne property name
someOne.name = 'cover';
//or use (2) 
someOne['name'] = 'cover';
// or use (3) defineProperty
Object.defineProperty(someOne, 'name', {
	value : 'cover'
})

从上面看,貌似使用Object.defineProperty很麻烦,那为啥存在这样的方法呢?

带着疑问,我们来看下 Object.defineProperty的定义。


what is Object.defineProperty

The Object.defineProperty() method defines a new property directly on an object, or modifies an exisiting property on an object, and returns the object.

从上面得知,我们可以通过Object.defineProperty这个方法,直接在一个对象上定义一个新的属性,或者是修改已存在的属性。最终这个方法会返回该对象。

语法

Object.defineProperty(object, propertyname, descriptor)

参数

  • object 必需。 要在其上添加或修改属性的对象。 这可能是一个本机 JavaScript 对象(即用户定义的对象或内置对象)或 DOM 对象。
  • propertyname 必需。 一个包含属性名称的字符串。
  • descriptor 必需。 属性描述符。 它可以针对数据属性或访问器属性。

属性的状态设置

其中descriptor的参数值得我们关注下,该属性可设置的值有:

  • 【value】 属性的值,默认为 undefined。
  • 【writable】 该属性是否可写,如果设置成 false,则任何对该属性改写的操作都无效(但不会报错),对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。
var someOne = { };
Object.defineProperty(someOne, "name", {
    value:"coverguo" , //由于设定了writable属性为false 导致这个量不可以修改
    writable: false 
});  
console.log(someOne.name); // 输出 coverguo
someOne.name = "linkzhu";
console.log(someOne.name); // 输出coverguo
  • 【configurable]】如果为false,则任何尝试删除目标属性或修改属性以下特性(writable, configurable, enumerable)的行为将被无效化,对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。
var someOne = { };
Object.defineProperty(someOne, "name", {
    value:"coverguo" ,
    configurable: false 
});  
delete someOne.name; 
console.log(someOne.name);// 输出 coverguo
someOne.name = "linkzhu";
console.log(someOne.name); // 输出coverguo
  • 【enumerable】 是否能在for-in循环中遍历出来或在Object.keys中列举出来。对于像前面例子中直接在对象上定义的属性,这个属性该特性默认值为为 true。

注意
在调用Object.defineProperty()方法时,如果不指定, configurable, enumerable, writable特性的默认值都是false,这跟之前所
说的对于像前面例子中直接在对象上定义的属性,这个特性默认值为为 true。并不冲突,如下代码所示:

//调用Object.defineProperty()方法时,如果不指定
var someOne = { };
someOne.name = 'coverguo';
console.log(Object.getOwnPropertyDescriptor(someOne, 'name'));
//输出 Object {value: "coverguo", writable: true, enumerable: true, configurable: true}

//直接在对象上定义的属性,这个特性默认值为为 true
var otherOne = {};
Object.defineProperty(otherOne, "name", {
    value:"coverguo" 
});  
console.log(Object.getOwnPropertyDescriptor(otherOne, 'name'));
//输出 Object {value: "coverguo", writable: false, enumerable: false, configurable: false}
  • 【get】一旦目标对象访问该属性,就会调用这个方法,并返回结果。默认为 undefined。
  • 【set】 一旦目标对象设置该属性,就会调用这个方法。默认为 undefined。

从上面,可以得知,我们可以通过使用Object.defineProperty,来定义和控制一些特殊的属性,如属性是否可读,属性是否可枚举,甚至修改属性的修改器(setter)和获取器(getter)

那什么场景和地方适合使用到特殊的属性呢?


实际运用

在一些框架,如vue、express、qjs等,经常会看到对Object.defineProperty的使用。那这些框架是如何使用呢?

MVVM中数据‘双向绑定’实现

如vue,qjs等大部分mvvm框架(angular用的是脏处理)都是通过Object.defineProperty来实现数据绑定的
为了更详细的说明,我将在下一篇文章跟大家讲解下。下面篇幅先不展开。(别扔砖。。。)

优化对象获取和修改属性方式

这个优化对象获取和修改属性方式,是什么意思呢?
过去我们在设置dom节点transform时是这样的。

//加入有一个目标节点, 我们想设置其位移时是这样的
var targetDom = document.getElementById('target');
var transformText = 'translateX(' + 10 + 'px)';
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;

通过上面,可以看到如果页面是需要许多动画时,我们这样编写transform属性是十分蛋疼的。(┬_┬)

但如果通过Object.defineProperty, 我们则可以

//这里只是简单设置下translateX的属性,其他如scale等属性可自己去尝试

Object.defineProperty(dom, 'translateX', {
set: function(value) {
         var transformText = 'translateX(' + value + 'px)';
        dom.style.webkitTransform = transformText;
        dom.style.transform = transformText;
}
//这样再后面调用的时候, 十分简单
dom.translateX = 10;
dom.translateX = -10;
//甚至可以拓展设置如scale, originX, translateZ,等各个属性,达到下面的效果
dom.scale = 1.5;  //放大1.5倍
dom.originX = 5;  //设置中心点X
}

上面只是个简单的版本,并不是最合理的写法,但主要是为了说明具体的意图和方法

有兴趣了解更多可以看下面这个库:https://github.com/AlloyTeam/AlloyTouch/blob/master/transform.js

增加属性获取和修改时的信息

如在Express4.0中,该版本去除了一些旧版本的中间件,为了让用户能够更好地发现,其有下面这段代码,通过修改get属性方法,让用户调用废弃属性时抛错并带上自定义的错误信息。

[
  'json',
  'urlencoded',
  'bodyParser',
  'compress',
  'cookieSession',
  'session',
  'logger',
  'cookieParser',
  'favicon',
  'responseTime',
  'errorHandler',
  'timeout',
  'methodOverride',
  'vhost',
  'csrf',
  'directory',
  'limit',
  'multipart',
  'staticCache',
].forEach(function (name) {
  Object.defineProperty(exports, name, {
    get: function () {
      throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
    },
    configurable: true
  });
});

其他如设置常量等用途。。。


#兼容
最后注意下,Object.defineProperty是ES5的属性,大部分场景使用是没问题的, 但在一些场景如IE8以下是使用不到的哈。

QQ天气H5-前端完整解析

QQ天气H5-前端完整解析

前言:
什么是手Q天气

手Q天气是在手Q 6.0版本以上新增的功能,页面会展现当天的气温情况,已经五天温度折线图以及24小时温度图表等。
并且为了更好的交互效果,天气页面会根据8种不同的天气信息,展现相应的天气动画。如下雨下雪,飘云,日光闪烁等动画效果。

在开发手Q天气的时候,学习到许多,发现有许多地方值得写一下。以下是我的总结。


一、REM整体布局

我们知道对于移动端来说,分辨率适配是个常见的问题,设计师往往给与我们的是iphone6(750px)的视觉稿。而对于天气页面来说采用REM来整体布局是个十分不错的选择。

rem 是什么

rem(font size of the root element)是指相对于根元素的字体大小的单位。简单的说它就是一个相对单位。rem计算的规则是依赖根元素。

基本写法

如下面定义html的节点

/*定义html根元素字体大小*/
html{
	font-size:10px;   
}
/*定义子元素,采用rem作为单位*/
.sonDom {
    width: 6rem;  /*相当于 6*10=60px*/
    height: 3rem;   /*相当于 3*10=30px*/
    line-height: 3rem;   /*相当于 3*10=30px*/
    font-size: 1.2rem;   /*相当于 1.2*10=12px*/
    border-radius: .5rem;   /*相当于0.5*10=5px*/
}

从上面可以看出,rem其实相当于一个划算单位

举个例子,我们平时理解的 1米=10分米=100厘米

同理可得: 1rem = 根元素font-size的值

这里一开始设置了 html根元素font-size为10px,即 1rem = 10px

通过上面的,我们可以得出

可以通过改变html的font-size的值而等比改变所有用了rem单位的元素

而这个正是rem实现完美的分辨率适配的原理。

如何动态更改根元素font-size值

为了实现分辨率适配,我们需要用根据屏幕的大小动态去计算根元素的font-size的值
目前普遍的是两种方法:

1、通过媒体查询方式

通过媒体查询的方式,能够满足大部分场景,只需要把常用的屏幕宽度考虑进去即可

/*默认为20px*/
html {
    font-size : 20px;
}
/*判断宽度设置不同的html font-size值去覆盖默认值*/
@media only screen and (min-width: 320px){
    html {
        font-size: 10px;
    }
}
@media only screen and (min-width: 375){
    html {
        font-size: 16; 
    }
}
@media only screen and (min-width: 414px){
    html {
        font-size: 20px; 
    }
}

2、通过js设置

如果希望把所有屏幕大小给考虑进去,可以考虑使用js来计算(天气H5也是使用js来换算),如下面的代码

 //设置fontsize
   var doc = document,
   win = window;
   function initFontSize  () {
        var docEl = doc.documentElement,
            resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
            recalc = function() {
                var clientWidth = docEl.clientWidth; //window.innerWidth;
                if (!clientWidth) return;
                fontSizeRate = (clientWidth / 375);
                var baseFontSize = 15 * fontSizeRate;
                docEl.style.fontSize = baseFontSize + 'px';
            };

        recalc();
        if (!doc.addEventListener) return;
        win.addEventListener(resizeEvt, recalc, false);
        doc.addEventListener('DOMContentLoaded', recalc, false);
    };

rem换算

我们在开发的时候,往往不希望涉及到rem的换算,如一个设计稿按钮是120px,如果我们根节点font-size是14px,
计算 120/14=8.571rem。感觉还是挺麻烦得
而这些工作是可以通过sass等预处理器或者构建去完成

如下面 翔神写的fis插件

https://github.com/imweb/fis-parser-rem

效果图

如下面, rem的适配效果还是挺不错的

rem需注意点

在移动端使用rem的话,兼容性没问题的。但还是存在着一些需要注意的地方:

1、小数数值处理

不同浏览器计算rem转换为px数值时,对于小数点后的数值的处理是有所偏差,rem计算偏差的根源是浏览器内核数字类型的区别,如果浏览器的内核数字类型是float类型,能够较好地支持有小数点的数值。当浏览器内核数字类型是int类型,不支持小数点,会对数字进行四舍五入,这样就会有偏差。如果元素越大偏差得就越明显!

2、雪碧图rem

使用rem的同时又涉及到雪碧图时,由上面我们可以得知,rem的换算成px的尺寸非严格精确尺寸,如果雪碧图如果图标之间的距离过小,就可能导致图标过界,因此图与图之间的间隙需要留相应大一点。

3、单纯的rem没解决高度适配的问题。

单纯的rem没解决高度适配的问题,当然目前也没有特别多高度适配的场景,因此建议如果需要在使用rem基础上还做相应的高度适配,就要通过相应的js去辅助啦。


2、弹性盒子局部布局

我们发现,过去对于页面均分的布局时,我们比较常用下面的方式:

  • 使用float浮动子元素的方法,但需要注意清除浮动

  • 或者是设置子元素为inline-block,但需要注意设置子元素margin值

或许我们可以考虑下css3弹性盒子模型。

兼容性

让人惊喜的是目前的主流智能移动设备操作系统Android和ios的内嵌浏览器对其也有不错的支持。对移动开发来说这真是太美好了,至少对于不太喜欢使用float,padding的我来说是这样的。

手Q天气的使用

如下面这样的布局整个div分成5个部分,每个部分占据同样的宽度。

####上面的html结构如下

通过弹性盒子模型,设置其css代码如下:

/*设置容器为盒子*/
.info-day-list {
    display: -webkit-box;
    padding: 1rem 0;
}
/*设置item*/
.info-day-item {
    -webkit-box-flex: 1;  /*设置item占据的比例*/
    width: 1%;
    font-size: 1.4rem;
    line-height: 3rem;
    text-align: center;
}

通过上面,可以发现,弹性盒子将模块的所拥有的空间进行我们自定义比例去分配。上面每个item设置的
box-flex都为1,故其都有父容器剩余空间1比重的宽度。

需注意点

1、弹性盒子模型div块因为文字内容不同而不均分

在开发的时候,我发现在使用弹性盒子模型时,如果涉及到文字的时候需要注意

由于天气的描述文字长度不同,如西南风和微风,分别是三个字和两个字。会有不同的宽度而导致不均分

如上面css所示,我设置了子元素width为1%(只有设置了item是统一的width就行,不一定需要是1%)就可以解决这个问题


HTML5 canvas

我们可以看到在页面中带有温度折线图以及下雪下雨的动画,这个时候我们发现使用dom去绘制这样稍微复杂的动画时,性能并不好也不好操作。这时候我们可以考虑使用到HTML5的canvas画布去实现了。这样可规避渲染树的计算,使渲染更快

由于代码比较篇幅较长,这里只给最终生成效果哈。

折线图表

###下雨下雪动画

效果如下, 发现使用canvas在绘制这些动画的时候,还是十分方便的。

具体实现可以看下面这个文章 - 前端如何呼风唤雨

canvas需注意点

1、canvas高清屏模糊

在绘制折线图的时候,我们发现,折线图在高清屏下十分模糊,这是为什么呢?

熟悉retina屏的同学应该都知道,在浏览器的window变量中有一个devicePixelRatio的属性,该属性决定了浏览器会用几个(通常是2个)像素点来渲染1个像素,举例来说,假设devicePixelRatio的值为2,一张100x100像素大小的图片,在retina屏幕下,会用2个像素点的宽度去渲染图片的1个像素点,因此该图片在retina屏幕上实际会占据200x200像素的空间,相当于图片被放大了一倍,因此图片会变得模糊。

因此我们的解决方案时:更加屏幕像素比devicePixelRatio的小同比方法canvas

如下面代码

  //兼容高清屏幕,canvas画布像素也要相应改变
  var c = document.getElementById("canvas");
  //获取devicePixelRatio
  var DPR = window.devicePixelRatio;画布宽高
  //同比设置画板宽高
  c.width = = canvasWidth * DPR;
  c.height = canvasHeight * DPR;

   

2、内存占用

canvas对内存的消耗是挺大的,如非必要还是不要使用多个canvas


css3 transition animation

我们可以使用CSS3的transition和animation来实现许多交互效果。

使用transition实现滑动Slider

在天气内页有个星座slider,如下面

通过设置每个卡片的类名,使其切换不同的位置

.star-icon-outside-l {
    z-index: 20;
    -webkit-transform: translateX(-18.8rem) scale(.673);
            transform: translateX(-18.8rem) scale(.673);
}
.star-icon-outside-r {
    z-index: 20;
    -webkit-transform: translateX(8.5rem) scale(.673);
            transform: translateX(8.5rem) scale(.673);
}
.star-icon-beside-l {
    z-index: 40;
    -webkit-transform: translateX(-12.5rem) scale(.851);
    -webkit-transform: translateX(-12.5rem) scale(.851);
}
.star-icon-beside-r {
    z-index: 40;
    -webkit-transform: translateX(2.35rem) scale(.851);
            transform: translateX(2.35rem) scale(.851);
}
.star-icon-hide-l {
    z-index: 10;
    -webkit-transform: translateX(-25rem) scale(.673);
            transform: translateX(-25rem) scale(.673);
}
.star-icon-hide-r {
    z-index: 10;
    -webkit-transform: translateX(17rem) scale(.673);
            transform: translateX(17rem) scale(.673);
}
.star-icon-cur {
    z-index: 90;
    -webkit-transform: translateX(-5.05rem) scale(1);
            transform: translateX(-5.05rem) scale(1);
}

animation循环动画

1、渐隐渐现

@-webkit-keyframes toggleShow
{
    0% {
        opacity: 0;
    }
    11% {
        opacity: 0;
    }
    12.5% {
        opacity: 1;
    }
    20%{
        opacity: 0;
    }
    100%{
        opacity: 0;
    }
}

2、放大收缩

@-webkit-keyframes shine
{
    0% {
        -webkit-transform: scale(1,1);
    }
    50% {
        -webkit-transform: scale(1.2,1.2);
    }
    100% {
        -webkit-transform: scale(1,1);
    }
}

3、飘动

@-webkit-keyframes moveToLeft /* Safari 和 Chrome */
{
    from {-webkit-transform: translate(0);}
    to {-webkit-transform: translate(-33.33333333%);}
}

优化

我们知道,在移动端开发,性能和加载速度是十分重要的,这里我们就需要考虑所有前端能优化的点,做好了优化,
才能时你这个页面更好的展示。以下是相关的优化内容

基本动画优化

基本的动画优化,如使用transform的translate来代替left等位移操作
3D加速等,

合理使用RAF(requestAnimationFrame)

使用raf能解决脚本问题引起的丢帧,卡顿问题,并且支持中间状态监听

//首页判断是否可以使用requestAnimFrame来替换setTimeout
window.requestAnimFrame =
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    function(callback) {
        return window.setTimeout(callback, 1000 / 30);
    };

canvas优化

使用canvas实现下雨下雪效果,是通过一帧一帧地去重绘下落的雨滴或者雪花。这里雪花雨滴越多,对手机的性能要求就越高。

因此我们需要对不同的手机进行处理,对于一些稍微低端的手机进行一些降级处理和优化
根据渲染情况,相应的减少雨滴和雪花的个数,减少渲染计算时间

//判断每次update的时间差,如果发现时间长过长,则相应地减少动画的最大雪花个数
 if (new Date - lastTime > 30 && drops.length < OPTS.maxNum && OPTS.maxNum > 21) {
      OPTS.maxNum -= 10;
 }

内存优化

由于我们打开天气广告时,是新开一个webview的
因此我们需要暂停被遮住的天气webview的天气动画,减少内存消耗

if(mqq&&mqq.iOS&&mqq.addEventListener){
      mqq.addEventListener("qbrowserVisibilityChange", function(e){
          cancelAnimationFrame(drp_ticker);
          if(!e.hidden){
              update();
          }
      });
  }

合理使用缓存

为了加快二次加载页面的速度,我们就需要使用好缓存。

天气的数据,都会用localstorage缓存起来

第二次短时间加载则会使用localstorage的数据,加快二次加载速度。

 //判断是否有可用的缓存
if (checkCache('weather-local_weather_info')) {
    //有则使用缓存
    Page.processData(local_weather_info);
    weather_info = local_weather_info;
} else {
	//没有缓存则去请求
    Vinda.getData('data-weatherInfo', function(data) {
        if (data && data.retcode == 0) {
            Page.processData(data);

            //将天气信息存储进来,新增时间戳,用于缓存新鲜度的判断
            $.extend(data.result, {
                searchTime: +Date.now(),
                city: city
            });
            //每次获取都会更新缓存
            $S.save({
                key: 'local_weather_info',
                value: data
            });
    });
}

预加载

DNS预解析

我们可以通过dns 预解析prefetch,提前解析,减少dns请求时间

<link rel="dns-prefetch" href="//pub.idqqimg.com" />

CGI预加载

由于天气页面是强数据页面,对于cgi数据是强依赖的。因为提前预先加载cgi能够使我们更快地去渲染页面而不是等先拉取页面js再去执行页面js去请求cgi的这样的顺序。

代码优化

dom对象池复用

在天气内页有个星座slider,如下面

我们知道总共有12个星座,但我这里却只使用了7个dom(5个可见,2个分别是隐藏的),通过复用来实现循环的12个月。虽然这里并没有减少很多dom的数量,但我觉得dom对象池复用的**能给多dom节点的的场景带来质的飞跃。

异步加载权重较低的模块

由于整个天气又有折线图,又有动画,又有下雨下雪等东西。因此我们需要对页面进行模块划分。每个部分都是一个模块
我i将天气页面大致分成下面几个模块

//页面总模块
var Page = {/**/};

//头部模块
var headerMod = {/**/};
//时间维度的温度变化图
var timeDegreeMod = {/**/};
//广告模块
var adsMod = {/**/};
//一周天气情况图
var detailMod = {/**/};
//天气动画模块
var AnimationMod ={/**/}

然后通过总模块去管理子模块,由于模块的划分,我们可以很轻松的根据页面展示权重和先后顺序,分别去渲染和异步加载相应的模块

var Page = {
	render: function(){
    	//渲染基本页面
        headerMod.render();
        timeDegreeMod.render();
    	detailMod.render();
        //加载完天气信息才去加载广告;
        adsMod.getAds();
        //由于权重较低,因此异步加载下雨下雪的动画部分组件
        require.async('./setAnimation', function(AMod) {
            AnimationMod = AMod;
            AnimationMod.init();
        });
    }
}

其余基本优化

雪碧图,文件合并等减少请求数

资源压缩,代码压缩减少请求体积。

内联css, js置后等渲染无阻塞


兼容点

在开发手Q天气时,还遇到下面一些需要兼容和注意的点:

ios 广点通app广告处理逻辑兼容

由于手Q天气涉及到广告,大部分广点通广告是只需要点击链接跳转就可以了

但有些广告由于是app广告,需要引导用户去下载,故在ios上则需要做些兼容。在ios手机需要通过以下判断,改为呼起app store下载页面

  //判断是否为手Q打开且为ios且为app下载广告
var isIosAppAds = mqq.iOS && mqq.device.isMobileQQ() && producttype == 19;
//如果是app ios 广告,
//则jsonp请求广点通给的跳转链接,获取跳转appstore的tencent串
if (self.isIosAppAds) {
    $.ajax({
        url: adsMod.jump_url,
        data: {
            acttype: 1,
        },
        dataType: 'jsonp',
        success: function(o) {
            if (o && o.ret >= 0) {
                var data = o.data || {};
                if (data.dstlink) {
                	//通过手Q接口呼起app
                    mqq.app.launchApp({
                        name: data.dstlink //self.dstlink
                    });
                }
            }
        }
    });
} else {
    //默认打开新webview即可
    mqq.ui.openUrl({
        url: adsMod.jump_url,
        target: 1,
        style: 0
    });
}

X5内核兼容点

由于我们的场景是在手Q上打开,故需要兼容X5内核上的规范。

X5 tbs.1x版本时,伪元素是不能做动画的。

X5 tbs.1x版本时,不支持transition-timing-function 的ease-out曲线

目前了解,貌似到了X5 tbs.2x版本正在开始灰度支持。

更多x5上的问题,可以通过以下链接去查看QQ浏览器官网的 X5技术指南

非a标签跳转 bug

因为天气页面有许多跳转上报,需要先上报再跳转,然后我之前是这样写的

<div id="js-jump-xxx" onclick="reportAndJump();"></div>

但这样写发现在低版本的android机上,只能发起上报请求而不能进行链接跳转

后来经排查,发现低端android机只能使用a标签进行跳转操作

<a id="js-jump-xxx" onclick="reportAndJump();"></a>

不足

  • 由于页面涉及到比较多的动画和canvas,故对内存的消耗比较高,这方面一直没有很好去解决低内存手机的内存消耗问题

  • 页面动画渲染和代码仍需雕琢。当时刚入职,许多方面还是一知半解。故自我觉得仍然有许多可以优化的地方。。。

总结

QQ天气H5是我毕业(2015)来到腾讯的第一个独立开发的项目。虽然现在已经交接了。但我时不时都会去看下这个项目的动态和代码提交记录。QQ天气H5这个项目,让我在刚入职时学会了许多。虽然写得并不是很好啦。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.