渲染管道最重要的事情:每个步骤,都是前一个操作的结果用于后一个操作的数据。如果为元素设置动画,每一帧都要进行相同的处理操作,大多数浏览器的1帧/秒如果浏览器丢失了中间部分帧,就会让人觉得卡顿。
时间轴上的动画帧
真正绘制一个页面
光栅化
简单光栅处理示意图
现在知道了文档的结构,CSS,布局树,绘制顺序。就是将数据转化为物理设备上的像素了。这个过程成为光栅化。
合成
合成处理是将页面的各个部分光栅化,并且合成线程进行图层移动合成。
分层
为了分清哪些元素位于什么图层,主线程遍历布局树创建图层树,如果某些部分是单独图层(例如划入式侧面菜单栏),但没有拆分出来,可以用CSS属性:will-change提示浏览器。
主线程的光栅化和合成
一旦创建了图层树和确定了绘制顺序,主线程将会把信息传递给合成线程,接着,合成线程会光栅化每个图层,一个图层可能跟页面一样大,合成线程将其分块后发送给光栅线程。光栅线程光栅化每个小块后将他们存储在显存中。
光栅线程创建分块的位图并发送到 GPU
合成线程会不同的光栅化线程设置优先级,以便视图或者附近区域的画面可以先光栅化显示。图层还具有不同的分辨率的块,可以放大显示。
一旦块被光栅化,合成线程会收集这些块的信息(称为绘制四边形),创建合成帧。
绘制四边形:包含块在内存的位置,以及合成时块在页面中的位置等信息。
合成帧:一个绘制四边形的集合,代表一个页面的一帧。
接着,合成帧通过IPC提交给浏览器进程,此时,可以在UI线程或者其他插件的渲染进程添加一个合成帧,这些合成器帧被送到GPU然后在屏幕上显示。如果收到滚动事件,合成帧会创建另一个合成帧到GPU。
合成线程创建合成帧,将其发送到浏览器进程,再接着发送到 GPU
用户输入行为与合成器
用户的任何行为,对于浏览器来说都是输入行为。包括点击,滚动,触摸屏幕,滑动鼠标。
例如用户触摸屏幕时,浏览器进程率先捕捉行为,浏览器进程所掌握的信息仅限于行为发生的区域,因为标签内的内容都由渲染进程处理。浏览器进程会将点击行为以及坐标传达给渲染进程。渲染进程在进行相应的处理。
合成器接收输入事件
悬于页面图层的视图窗口
理解非立即可滚动区
运行JS是渲染进程的主线程的工作,页面合成之后,注册了事件的区域叫做“非立即可滚动区”,合成器线程会通知渲染进程的主线程处理。没有输入事件没有发生在事件注册区域,合成器进程则不需要等待主线程,可以继续合成帧。
设置时间处理应该注意
document.body.addEventListener('touchstart', event => { if (event.target === area) { event.preventDefault(); } }); |
事件代理是浏览器常用的事件处理模式,在顶层元素添加一个事件。
这样带来的问题就是整个页面都被标识为非立即滚动区域,合成器进程需要每次都询问主线程是否需要处理事件并且等待反馈。流畅的合成器处理模式就失效了。
你可以给事件监听添加一个 passive:true 选项 ,将这种负面效果最小化。这会提示浏览器你想继续在主线程中监听事件,但合成器不必停滞等候,可接着创建新的合成帧。
document.body.addEventListener('touchstart', event => { if (event.target === area) { event.preventDefault() } }, {passive: true}); |
不过上述写法可能又会带来另外一个问题,假设某个区域你只想要水平滚动,使用 passive: true 可以实现平滑滚动,但是垂直方向的滚动可能会先于event.preventDefault()发生,此时可以通过 event.cancelable 来防止这种情况。
document.body.addEventListener('pointermove', event => { if (event.cancelable) { event.preventDefault(); // 阻止默认的滚动行为 /* * 这里设置程序执行任务 */ } }, {passive:: true}); |
也可以使用css属性 touch-action 来完全消除事件处理器的影响,如:#area{touch-action:pan-x;}
查找事件对象
当组合器线程发送输入事件给主线程时,主线程首先会进行命中测试(hit test),来查找对应的时间目标,命中测试会基于渲染过程中生成的绘制记录(paint records)查找事件发生坐标下寻找的元素。
主线程检查绘制记录查询坐标 x、y 处绘制内容
#####事件的优化
一般我们屏幕的刷新速率为 60fps,但是某些事件的触发量会不止这个值,出于优化的目的,Chrome 会合并连续的事件(如 wheel, mousewheel, mousemove, pointermove, touchmove ),并延迟到下一帧渲染时候执行 。而如 keydown, keyup, mouseup, mousedown, touchstart, 和 touchend 等非连续性事件则会立即被触发。
使用 getCoalescedEvents 获取帧内事件
事件合并可帮助大多数 web 应用构建良好的用户体验。然而,如果你开发的是一个绘图类应用,需要基于 touchmove 事件的坐标绘制线路,那么在你试图画下一根光滑的线条时,区间内的一些坐标点也可能会因事件合并而丢失。这时,你可以使用目标事件的 getCoalescedEvents 方法获取事件合并后的信息。
window.addEventListener('pointermove', event => { const events = event.getCoalescedEvents(); for (let event of events) { const x = event.pageX; const y = event.pageY; // 使用 x、y 坐标画线 } }); |
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。