移动端吸顶、动画、浏览器层模型以及相关总结

来源:互联网 发布:远程教育软件哪家好 编辑:程序博客网 时间:2024/05/21 22:56

总结

问题归纳

  1. 顶部通知公告条自动吸顶判断

  2. 点击自动展开动画,兼容Android与ios

  3. 页面中的图标与文字对齐(垂直居中对齐、水平居中对齐)

  4. 移动端居中、间距自适配

  5. h5页面自定义native顶部导航条

知识点归纳

  • 惯性滑动的事件触发机制

  • iscroll模拟滚动,滚动事件在安卓中未被触发

  • requestAnimateFrame(fn),优化动画性能

  • position:sticky; 滚动浮屏显示

  • transition:height 1s; 安卓卡顿

  • box、flexbox、flex布局,兼容性差异

  • rem移动端适配方案,font-size计算

  • JSBridge h5与原声native的通信

展开收缩

demo2.gif

在移动端要求实现点击后,展开或收缩内容区域,承载app在ios系统中使用的webkit 602.4.6内核,安卓中使用的U3 1.0.0.100内核。

尝试利用css3的动画效果,利用 transition:height .5s 进行高度过渡,但是该效果在安卓机上出现了明显的卡顿现象。

在硬件的配置上来说,用于测试的安卓机并不差,因此猜测出现卡顿现象的原因在于内核的渲染机制上,在网上恶补了下相关知识后终于有了发现。

主线程与合并线程

在浏览器中,包含这三个线程:

  1. JS引擎线程

  2. 事件触发线程

  3. GUI渲染线程

其中GUI渲染与JS执行引擎是互斥的,也就是存在js执行阻塞界面渲染的情况。

在这三个线程之上,浏览器拥有两个重要的执行线程来渲染页面:

  1. 主线程 (Main Thread)

  2. 合并线程 (Compositor Thread)

主线程(Main Thread)主要负责Js的执行、布局样式的计算以及绘制(paint,将图像层的内容绘制到位图中),故应包含JS引擎和事件触发线程。

合并线程(Compositor Thread)利用GPU来绘制位图到屏幕以及对位图的显示进行计算判断,故应包含GUI渲染线程。

一个简单的滑动例子便能说明其中的区别。

demo1.png

用户在滑动时,合并线程将直接利用GPU对之前的位图进行计算和展示,并不需要主线程的参与,而若是绑定了事件处理器,则会如下图所示。

demo2.png

主线程会参与执行对应的事件处理函数,合并线程会等待函数执行完成后再继续工作,显示设备刷新频率一般在50~60,那么60fps是能够得到最佳的动画显示,也就是说处理函数必须是在 1000 / 60 = 16.67ms之内完成,否则便会出现卡顿抖动的现象。

渲染-层模型

在Chrome中实际上有几种不同类型的层:掌管DOM子树的渲染层(RenderLayer)以及掌管渲染层子树的图形层(GraphicsLayer)。 我们这里最关心的是后者,因为作为纹理上传到GPU之中的就是图形层。

浏览器的渲染过程如下图所示:

其中的layout和paint是在每一个图层上进行的,当有一个元素经常变化,为了减少这个元素对页面的影响,我们可以为这个元素创建一个单独的图层,在浏览器paint位图会对图层进行分开渲染,最终再组合到一起,这样便不需要绘制整个网页,从而提高页面的渲染性能。

其概念分为了:

  1. RenderLayer

  2. GraphicsLayer

这篇文章详细的讲述了浏览器层模型这个概念。简单来说,一张页面由渲染层(RenderLayer)和图形层(GraphicsLayer)构成,这将便于主线程绘制位图,浏览器会认为图形层所占的区域不会经常变化导致回流(reflow)。图形层通常将交由GPU来进行绘制计算,如旋转、位移等。

因此对于图形层来说,主线程只需要绘制一次位图,之后便不会在做任何布局、绘制和提交位图给GPU

回到最初的问题上,展开和收缩之所以会导致卡顿,根本原因便在于该dom元素属于渲染层(Render Layer),在高度变化的过程中,主线程在不断的绘制位图,再交由合并线程绘制到屏幕,其流畅程度便取决于主线程的计算速度和合并线程读取位图的速度,如下图所示。

animate-height-2x.png
这里写了一个简单的demo来进行验证

<html lang="en"><head>  <meta charset="UTF-8">  <title>Document</title></head><body>   <div id="a" style="background:red;width:100px;height:100px;transition:height 2s">a</div>   <button id="b" >b</button>   <script>    document.getElementById('b').onclick = function() {        document.getElementById('a').style.height=800+'px'    }   </script></body></html>

观察点击后的timeline

timeline.png

timeline2.png

可以看到在Main中,不断的进行着位图绘制和合并层操作,这在dom节点多的页面中,高度的变化导致的回流(reflow)计算会更加的耗费性能。

一般的展开和收缩,可以利用 transform:scale(1)属性,拥有该属性的元素都会被认为是图形层(GraphicsLayer)。

animate-transform-2x.png

timeline3.png

效果很明显,Main只进行了一次位图paint,剩下的便是GPU在进行动画绘制。

GPU善于计算位图的2d和3d转化,如拉伸、压缩、位移、旋转等,这些位图(也就是图像层)往往不会导致其他元素位置变动,触发回流,同时也不会触发本身元素的重绘(repaint),简单来说,一切变化都在GPU中进行,这使得页面往往能够展示一些非常炫酷的动画效果,比如这个3D 行星运转,而这些效果的流畅度纯粹取决于GPU。

硬件加速

硬件加速是基于层模型的一种浏览器优化策略,利用GPU对纹理(也就是图层或位图)生成快照存储起来,不会在每一帧的渲染中都去通知主线程重新生成。

优雅降级

在本次场景中,所需要的并不是对元素的拉伸效果,而是对内容的展示,其效果肯定会影响后面的元素布局,触发回流,考虑到安卓机器良莠不齐,同时对于安卓浏览器,css动画的硬件加速只适用于transform和opacity,因此只能对其进行优雅降级……

参考文章:

  • Chrome 渲染优化 - 层模型

  • CSS3 3D 行星运转 && 浏览器渲染原理

  • 深入浏览器理解CSS animations 和 transitions的性能问题

  • 深度剖析浏览器渲染性能原理,你到底知道多少?

  • GPU:合成加速

  • 渲染性能

  • 页面性能优化实践总结

吸顶

demo3.gif

position:sticky

sticky是css3出的新特性,能够在滑动时,根据设置的top或bottom值,来自动切换成fixed或relative状态。

当在目标区域可见时,其表现为relative,当滑动超出屏幕时,表现为fixed,可以说是浏览器专门为吸顶这类功能而设计的,但在兼容方面有很大的问题,故只有换种思路。

sticky.png

监听scroll事件

由于在移动端fixed属性表现不佳,因此采用absolute进行替代,整体页面结构以绝对定位,分为上中下结构,中间容器元素装载滑动内容。

吸顶的“通知”元素以absolute定位于顶部,正常展示的“通知”元素则处于中间容器元素(同样以absolute定位),通过scroll监听滑动距离来选择是否展示吸顶元素。

-webkit-overflow-scrolling: touch

由于采用的H5页面,缺乏惯性滑动,体验不足,于是采用css3的-webkit-overflow-scrolling: touch属性来开启惯性滑动。

在惯性滑动中,出现了吸顶延迟的现象,网上一搜,原来这种问题已是老生常谈,究其根本,在于不同内核的触发机制不同。

在ios中,正常的触发机制是:

touchstart -> touchmove touchmove touchmove ……. touchmove -> touchend ->开始惯性滚动 -> 滚 滚 滚 … -> 惯性滚动结束 -> scroll

而在Android中,则是:

touchstart -> touchmove,scroll touchmove,scroll touchmove,scroll ……. touchmove,scroll -> touchend ->开始惯性滚动 -> 滚,scroll 滚,scroll 滚,scroll … -> 惯性滚动结束 -> scroll

但是不同的安卓手机具体的触发机制会有所差异。

吸顶效果是通过实时监听滑动来判断位置,scroll事件在惯性滚动期间的触发时间不定,在ios中更是只在惯性滑动结束后触发,因此出现了延迟现象。

js模拟滚动

经由查阅资料,总结出两种解决方案:

  1. 使用native

  2. 使用js模拟滑动

自然而然的,尝试使用第二种方案,引入ISCroll库来模拟惯性滚动,该库使用transform属性对元素进行2d位移,开启图形层,避免了reflow,使用GPU来计算和渲染,能够大大的提高性能,最终在安卓上的流畅效果也证明了这点。

requestAnimateFrame

由于IScroll的滚动容器大小是固定的,在展开或收缩时,需要手动调用refresh来实时的刷新高度。

起初,我设置了interval来调用refresh,但即使在ios中,也有明显的抖动,即便我将interval设置到了最小的时间10ms,仍然会有这种现象。

interval.png

显然,因为js的执行时间是不稳定的,interval有时在一幁里调用了多次,而有时又没有被调用,这导致其有时会丢失帧,同时,interval也会产生很多重复的计算,损耗大量性能。

最终,采用了requestAnimateFrame来取代interval,因为该函数会在每一帧开始时调用回调函数,这样就可以保证每一帧都能够只执行一次refresh了,当然前提是执行时间要控制在3~4ms内。

参考链接:

  • 渲染性能原理
0 0
原创粉丝点击