遇到的那些必须弄清楚的关于高性能动画的知识点

问题1:60Hz和60fps有什么关系?

没有任何关系。fps代表GPU渲染画面的频率,Hz代表显示器刷新屏幕的频率。一幅静态图片,你可以说这副图片的fps是0帧/秒,但绝对不能说此时屏幕的刷新率是0Hz,也就是说刷新率不随图像内容的变化而变化。游戏也好浏览器也好,我们谈到掉帧,是指GPU渲染画面频率降低。比如跌落到30fps甚至20fps,但因为视觉暂留原理,我们看到的画面仍然是运动和连贯的。

实际情况会比以上想象的复杂的多。即使你能给出一个固定的延时,解决60Hz屏幕下丢帧问题,那么其他刷新频率的显示器应该怎么办,要知道不同设备、甚至相同设备在不同电池状态下的屏幕刷新率都不尽相同。

以上同时还忽略了屏幕刷新画面的时间成本。问题产生于GPU渲染画面的频率和屏幕刷新频率的不一致:如果GPU渲染出一帧画面的时间比显示器刷新一张画面的时间要短(更快),那么当显示器还没有刷新完一张图片时,GPU渲染出的另一张图片已经送达并覆盖了前一张,导致屏幕上画面的撕裂,也就是是上半部分是前一张图片,下半部分是后一张图片:

Javascript高性能动画与页面渲染这个文章的图中,例如在14ms的时候,那么只有所有的帧数加起来时间超过16.7ms,那么肯定会丢帧,而每一帧延时这时候是2.7ms,因此16.7/2.7=6也就是在第六帧的时候肯定会丢帧!!如果更新频率为16ms,那么每一帧延迟为0.7ms,那么在16.7/0.7=23.85也就是在24帧的时候肯定会掉帧!!!而且如果动画setInterval的时间间隔越小,那么掉帧的数量会越多!

PC游戏中解决这个问题的方法是开启垂直同步(v-sync),也就是让GPU妥协,GPU渲染图片必须在屏幕两次刷新之间,且必须等待屏幕发出的垂直同步信号。但这样同样也是要付出代价的:降低了GPU的输出频率,也就降低了画面的帧数。以至于你在玩需要高帧数运行的游戏时(比如竞速、第一人称射击)感觉到“顿卡”,因为掉帧。(丢帧导致的问题就是卡顿,因为在运行其他的任务去了)

但如果你的回调函数耗时真的很严重,rAF还是可以为你做一些什么的。比如当它发现无法维持60fps的频率时,它会把频率降低到30fps,至少能够保持帧数的稳定,保持动画的连贯。

问题2:UI引擎和JS引擎是如何互斥的?

function jank(second) {
    var start = +new Date();
    while (start + second * 1000 > (+new Date())) {}
}
div.style.backgroundColor = "red";
// some long run task
//UI引擎和JS引擎互斥,先调用UI引擎,然后JS引擎,最后又是UI引擎
jank(5);
div.style.backgroundColor = "blue";

无论在任何的浏览器中运行上面的代码,你都不会看到div变为红色,页面通常会在假死5秒,然后容器变为蓝色。这是因为浏览器的始终只有一个线程在运行(可以这么理解,因为js引擎与UI引擎互斥,浏览器不会马上更新UI,而是会采用队列化修改批量更新的方式来完成的,除非是特殊的属性如innerWidth等,否则肯定会先执行后面的JS代码的)。虽然你告诉浏览器此时div背景颜色应该为红色,但是它此时还在执行脚本,无法调用UI线程。

var div = document.getElementById("foo");
var currentWidth = div.innerWidth;
div.style.backgroundColor = "blue";
//这里的UI也不会马上更新,而是会等到后面的JS运行结束后才会执行的
// do some "long running" task, like sorting data

我们可以用下面的代码进行优化:

requestAnimationFrame(function(){
    var el = document.getElementById("foo");
    var currentWidth = el.innerWidth;
    el.style.backgroundColor = "blue";
});
// do some "long running" task, like sorting data

更新背景颜色的代码过于提前,根据前一个例子,我们知道,即使在这里告知了浏览器我需要更新背景颜色,浏览器至少也要等到js运行完毕才能调用UI线程;(延迟这部分的代码)假设后面部分的long
runing代码会启动一些异步代码,比如setTimeout或者Ajax请求又或者web-worker,那应该尽早为妙(让后续代码马上执行)。

问题3:window.onscroll的弊端有哪些

像scroll,resize这一类的事件会非常频繁的触发,如果把太多的代码放进这一类的回调函数中,会延迟页面的滚动,甚至造成无法响应。所以应该把这一类代码分离出来,放在一个timer中,有间隔的去检查是否滚动,再做适当的处理。原理其实是一样的,为了优化性能、为了防止浏览器假死,将需要长时间运行的代码分解为小段执行,能够使浏览器有时间响应其他的请求。

var didScroll = false;
$(window).scroll(function() {
    didScroll = true;
});
setInterval(function() {
    if ( didScroll ) {
        didScroll = false;
        // Check your page position and then
        // Load in more results
    }
}, 250)

我们也可以采用requestAnimationFrame的方式完成处理:

var latestKnownScrollY = 0;
function onScroll() {
    latestKnownScrollY = window.scrollY;
}
function update() {
    requestAnimationFrame(update);
    var currentScrollY = latestKnownScrollY;
    // read offset of DOM elements
    // and compare to the currentScrollY value
    // then apply some CSS classes
    // to the visible items
}
// kick off
requestAnimationFrame(update);

这种方式不管我们的latestKnownScrollY是否发生变化,也就是是否发生滚动,那么我们都会不停的执行update方法,这显然是不需要的,例如用户在一个页面一直停留了半个小时而没有发生滚动,这时候的计算是没有必要的。

var latestKnownScrollY = 0,
    ticking = false;
//每次滚动的时候都会计算当前滚动的距离,同时也会采用requestAnimationFrame来更新下一帧的内容!
function onScroll() {
    latestKnownScrollY = window.scrollY;
    requestTick();
}
function requestTick() {
    if(!ticking) {
        requestAnimationFrame(update);
        //如果<下一帧执行完成后>这时候又可以继续监听滚动事件了,而不是像上面的例子每一次不管滚动
        //因此,即使你已经滚动的某个元素,但是因为这时候update没有执行,也就是上一帧还没有执行,因此不会继续往里面添加回调函数的,但是滚动的最新距离是可以获取到的
    }
    ticking = true;
}
function update() {
    // reset the tick so we can
    // capture the next onScroll
    ticking = false;
    var currentScrollY = latestKnownScrollY;
    // read offset of DOM elements
    // and compare to the currentScrollY value
    // then apply some CSS classes
    // to the visible items
}
window.addEventListener('scroll', onScroll, false);

注意:在这里不断的滚动是不会插入多个upate方法的,因为ticking为true了,所以只有等到下一帧执行了这个update方法才能继续插入upate,但是我们要注意,这时候页面的垂直滚动距离是不断变化的!

问题4:我们如何把一个函数推迟到下下帧来执行

(function(h5){
	if (!h5) throw new Error("animationFrame.h5ive: core.h5ive required.");
	var rAF = (window.requestAnimationFrame || window.msRequestAnimationFrame ||
			window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame ||
			window.oRequestAnimationFrame),
		cAF = (window.cancelAnimationFrame ||
			window.msCancelAnimationFrame || window.msCancelRequestAnimationFrame ||
			window.mozCancelAnimationFrame || window.mozCancelRequestAnimationFrame ||
			window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame ||
			window.oCancelAnimationFrame || window.oCancelRequestAnimationFrame),
		publicAPI, q_ids = {}
	;
     //产生一个独一无二的ID值
	function qID(){
		var id;
		do {
			id = Math.floor(Math.random() * 1E9);
		} while (id in q_ids);
		return id;
	}
	//插入下一帧,返回的是这个独一无二的数字
	function queue(cb) {
		var qid = qID();
		//通过这个独一无二的ID来向回调函数中放置回调函数。q_ids[111111113334]=12,q_ids[456789098]=24。调用返回的一个唯一的整数值(浏览器来返回的),通过这个整数值就能够取消这一次的调用!!!
		q_ids[qid] = rAF(function(){
			delete q_ids[qid];
			cb.apply(publicAPI,arguments);
			//这个元素的回调函数中的参数是除了回调函数以外给queue传入的参数
		});
		return qid;
	}
   //插入下下一帧,queue的作用是把函数放在下一帧中去执行,不过这个qid是全局唯一的!
	function queueAfter(cb) {
		var qid;
		qid = queue(function(){
			// do our own rAF call here because we want to re-use the same `qid` for both frames
			q_ids[qid] = rAF(function(){
				delete q_ids[qid];
				cb.apply(publicAPI,arguments);
			});
		});
		return qid;
	}
	//取消某一个回调
	function cancel(qID) {
		if (qID in q_ids) {
			cAF(q_ids[qID]);
			delete q_ids[qID];
		}
		return publicAPI;
	}
	function unsupported() {
		throw new Error("'requestAnimationFrame' not supported.");
	}
     //如果支持RAF那么就走这里的逻辑
	if (rAF && cAF) {
		publicAPI = {
			queue: queue,
			queueAfter: queueAfter,
			cancel: cancel
		};
	}
	else {
		publicAPI = {
			queue: unsupported,
			queueAfter: unsupported,
			cancel: unsupported
		};
	}
	h5.animationFrame = publicAPI;

})(this.h5);

因为每一个rAF函数都会返回一个独一无二的整数,而且这个整数是由浏览器决定的。通过这个整数我们可以取消某一个调用。

             function queue(cb) {
		var qid = qID();
		//通过这个独一无二的ID来向回调函数中放置回调函数。q_ids[111111113334]=12,q_ids[456789098]=24。调用返回的一个唯一的整数值(浏览器来返回的),通过这个整数值就能够取消这一次的调用!!!
		q_ids[qid] = rAF(function(){
			delete q_ids[qid];
			cb.apply(publicAPI,arguments);
			//这个元素的回调函数中的参数是除了回调函数以外给queue传入的参数
		});
		return qid;
	}

这个函数是把函数推迟到下一帧执行,而且每次都会产生一个独一无二的qid值作为数组的键,而值是rAF函数返回的一个唯一的值。当下一帧这个函数执行后那么我们就会从这个数组中清除相应的键和值,但是这里采用的delete删除,因此会留下undefined!

参考文献:

深入理解requestAnimationFrame

深入探究使JavaScript动画流畅的一些方法

时间: 2024-10-11 16:23:24

遇到的那些必须弄清楚的关于高性能动画的知识点的相关文章

swift_枚举 | 可为空类型 | 枚举关联值 | 枚举递归 | 树的概念

***************可为空的类型 var demo2 :we_demo = nil 上面这个代码串的语法是错的 为什么呢, 在Swift中,所有的类型定义出来的属性的默认值都不可以是nil 不管是普通简单值类型还是引用类型 那我就是要让这个属性默认值为空,为nil 怎么办呢,很简单,用语法,在定义这个属性的时,在类型后面声明一个? 这样就表示这个属性除了指定类型的默认值外还可以是一个可为空的类型 在Java中,最常见的错误类型就是NullPoinExecption, 为什么就是要有Nu

通过硬件层提高Android动画的性能

曾有许多人问我为什么在他们开发的应用中,动画的性能表现都很差.对于这类问题,我往往会问他们:你们有尝试过在硬件层解决动画的性能问题么? 我们都知道,在播放动画的过程中View在每一帧动画的显示时重绘自身.但如果你使用 View layer,使得View被渲染一次后就放到一个屏幕外的缓冲区中(即 layer),让View不断被重用,而不是一次又一次的重绘的话,这类动画性能问题就迎刃而解了. 此外,硬件层对图像的处理都会在GPU上进行缓存,使得我们在播放动画的过程中对View的特定操作的执行效率更高

自我总结(四) ---java web项目完结,j2ee的开始

自我完善的过程就是在不断的自我总结不断的改进. 前半个月刚好把项目做完了,项目也答辩了.总的来说吧,我觉得自己在java web这块知识上不算是彻彻底底把他弄懂了,就是说到的知识点都能够回答的上来一些.因为在答辩之前做了一些工作嘛!答辩成绩还好!其实刚开始做这个项目病虫害防治系统的时候,我觉得我一个人应该是做不完的.虽然表面上我说我会做完的,最后呢还是有一些功能没有去完善它,只是简单的去处理了一下.(对于信息的验证,我就全部一起验证,就没有一个一个的去验证.还有就是在管理员登陆的时候有个权限,这

20145216史婧瑶《信息安全系统设计基础》第7周学习总结

20145216史婧瑶<信息安全系统设计基础>第七周学习总结 教材内容总结 第六章 存储器层次结构 存储器系统是一个具有不同容量.成本和访问时间的存储设备的层次结构. CPU寄存器.高速缓存存储器.主存储器.磁盘. 第一节 存储技术 一.随机访问存储器(RAM) 1.RAM分类: 静态的SRAM-更快,更贵,作为高速缓存存储器,CPU片上或片下 动态的DARM-作为主存以及图形系统的帧缓冲区 2.非易失性存储器--ROM (1)分类 PROM-可编程ROM,只能被编程一次 EPROM-可擦写可

前端工程师必备收藏:学习资源全网罗

前言: 站在巨人的肩膀上,确实能让我们看得更远.而我认为学习的不二法门就是不断模仿,但是一定要有自己的思考.拿来主义如果少了思考,就会变得毫无意义.以下列出一些前端优秀网站,大家自行取其精华,批判吸收.  学习站点资源 Adobe出的,Adobe其实是html5非常忠实的拥抱者.官方社区有非常多的html5资源 http://beta.theexpressiveweb.com/ can i use,非常好,谁用谁知道 http://caniuse.com/ 前端观察站,腾讯的前端技术,挺有含金量

记第一场省选

其实,本来没什么好写的,我的确很水,如果正常发挥,应该是省赛边缘的水平,所以选不上也正常.可是第一天就出了一道题,发挥这么不正常,明显不应该,早上太糟心了,实在睡不着,起来总结了一下,感觉有以下几个原因.首先,太紧张.因为本来实力就不够,总担心这担心那,就更紧张了.第一道是模拟题,本来卡过那么多模拟题,这次又看到了很是心烦,但是还是要打,结果又犯了相同的错误,卡了两个多小时,实在是不应该……以后一定要注意细心,挑bug一定要全方面考虑,心态一定要稳,慌了就真慌了,模拟都是水题,相信自己,细心再细

2015年3月-前端开发月刊

1. 创业 2. 职业|项目管理 3. git 4. 知乎(zhihu) 5. 其它 6. Angular 7. 看书学习 8. 移动开发 9. Html5 10. CSS3 11. Javascript 前言: 技术只是一种手段,不是目的.人生的投资有很多种,很多个方向.技术只是其中的一个方向,是目前兴趣的所在点.兴趣会随着岁月的流逝,年龄的增长,会发生变化. 当然鸡蛋不能放在一个篮子里边,所以, 有空的时候提前看看,创业类的,产品,市场,营销类的书籍,提前给自己找点出路,免得后期技术路线走不

详解Java中代码块和继承

本文发表于个人GitHub主页,原文请移步详解Java中代码块和继承 阅读. 概念 1.代码块 局部代码块 用于限定变量生命周期,及早释放,提高内存利用率 静态代码块 对类的数据进行初始化,仅仅只执行一次. 构造代码块 把多个构造方法中相同的代码可以放到这里,每个构造方法执行前,首先执行构造代码块. 2.继承 继承是已有的类中派生出新的类,新的类能够吸收已有类的数据属性和行为,并能扩展新的功能. 代码块的执行顺序 public class Test {    public String name

前端学习资料汇总(转)

前端学习资料汇总(转) http://www.w3cfuns.com/blog-5402109-5400901.html 端工具: can i see :http://caniuse.com/  ——一个查看css及html5在各个浏览器及手机端的支持情况 前端视野:——平时可以多看看的网站了解下最新资讯前端观察站,腾讯的前端技术,挺有含金量:html5 中国:   http://www.html5cn.org/   ——有很多html及css小知识的网站,建议多浏览web前端开发   http