重绘与重排
浏览器下载完页面中的所有组件——HTML标记、JavaScript、CSS、图片——之后回解析并生成两个内部数据结构:
DOM树
表示页面结构
渲染树
表示DOM节点如何显示
重绘和重排操作都是代价昂贵的操作,它们会导致Web应用程序的UI反应迟钝。
重排何时发生?
- 添加或删除可见的DOM元素
- 元素位置改变。
- 元素尺寸改变(包括:外边距、内边距、边框厚度、宽度、高度等属性改变)。
- 内容改变,例如:文本改变或图片被另一个不同尺寸的图片代替。
- 页面渲染器初始化
- 浏览器窗口尺寸改变
每次重排都会产生计算消耗,因此一个好的提高程序响应速度的策略就是减少此类操作的发生。
改变样式
考虑这个例子:
var el = document.getElementById('mydiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
最糟糕的情况下会导致浏览器触发三次重排(大多现代浏览器做了优化,只会触发一次)。而且,这段代码四次访问DOM。可以被优化:
var el = document.getElementById('mydiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
上述代码会覆盖已存在的信息,若想保留原来信息,可以把它附加到cssText字符串后面:
el.style.cssText += '; border-left: 1px;';
另一种一次性修改样式的方法是修改CSS的class名称,而不是改内联样式。可能会带来轻微的性能影响,因为改变类时需要检查级联样式。
var el = document.getElementById('mydiv');
el.className = 'active';
缓冲布局信息
尽量减少布局信息的获取次数,获取后把它赋值给局部变量,然后在操作局部变量。
考虑一个例子,把myElement元素沿对角线移动,每次移动一个像素从100PX x 100PX 到 500PX x 500PX。
// 低效的
myElement.style.left = 1 + myElement.offsetLeft + 'px';
myElement.style.top = 1 + myElement.offsetTop + 'px';
if (myElement.offsetLeft >= 500) {
stopAnimation();
}
低效原因:每次元素每次移动是都会查询偏移量,导致浏览器刷新渲染队列而不利于优化。
current++
myElement.style.left = current + 'px';
myElement.style.top = current + 'px';
if (current >= 500) {
stopAnimation();
}
让元素脱离动画流
使用以下步骤可以避免页面中大部分重排:
- 使用绝对定位页面上的动画元素,将其脱离文档流
- 让元素动起来。
- 当动画结束时恢复定位,从而只会下移一次文档的其他元素。
事件委托
根据DOM标准,每个事件都要经历三个阶段:
- 捕获
- 达到目的
- 冒泡
如果你想要拦截所有点击事件,并且阻止其默认行为(打开链接),发送一个Ajax请求来获取内容,然后局部更新页面。用事件委托来实现这个过程,你只需要给所有链接的外层UI“menu”元素添加一个点击监听器,它会捕获并分析点击是否来自连链接。
document.getElementById('menu').onclick = function(e) {
// 浏览器target
e = e || window.event;
var target = e.target || e.srcElement;
var pageid, hrefparts;
//只关心hrefs,非链接点击退出
if (target.nodeName !== 'A') {
return;
}
// 从链接中找出页面ID
hrefparts = target.href.split('/');
pageid = hrefparts[hrefparts.length - 1];
pageid = pageid.replace('.html', '');
// 更新页面
ajaxRequest('xhr.php?page=' + id, updatePageContents);
// 浏览器组织默认行为并取消冒泡
if (typeof e.preventDefault === 'function') {
e.preventDefault();
e.stopPropagation();
} else {
e.returnValue = false;
e.cancelBubble = true;
}
};
跨浏览器兼容的部分包括:
- 访问事件对象,并判断事件源
- 取消文档树中的冒泡(可选)
- 组织默认动作(可选)