移动端吸顶、动画、浏览器层模型以及相关总结
来源:互联网 发布:远程教育软件哪家好 编辑:程序博客网 时间:2024/05/21 22:56
总结
问题归纳
顶部通知公告条自动吸顶判断
点击自动展开动画,兼容Android与ios
页面中的图标与文字对齐(垂直居中对齐、水平居中对齐)
移动端居中、间距自适配
h5页面自定义native顶部导航条
知识点归纳
惯性滑动的事件触发机制
iscroll模拟滚动,滚动事件在安卓中未被触发
requestAnimateFrame(fn),优化动画性能
position:sticky; 滚动浮屏显示
transition:height 1s; 安卓卡顿
box、flexbox、flex布局,兼容性差异
rem移动端适配方案,font-size计算
JSBridge h5与原声native的通信
展开收缩
在移动端要求实现点击后,展开或收缩内容区域,承载app在ios系统中使用的webkit 602.4.6内核,安卓中使用的U3 1.0.0.100内核。
尝试利用css3的动画效果,利用 transition:height .5s
进行高度过渡,但是该效果在安卓机上出现了明显的卡顿现象。
在硬件的配置上来说,用于测试的安卓机并不差,因此猜测出现卡顿现象的原因在于内核的渲染机制上,在网上恶补了下相关知识后终于有了发现。
主线程与合并线程
在浏览器中,包含这三个线程:
JS引擎线程
事件触发线程
GUI渲染线程
其中GUI渲染与JS执行引擎是互斥的,也就是存在js执行阻塞界面渲染的情况。
在这三个线程之上,浏览器拥有两个重要的执行线程来渲染页面:
主线程 (Main Thread)
合并线程 (Compositor Thread)
主线程(Main Thread)主要负责Js的执行、布局样式的计算以及绘制(paint,将图像层的内容绘制到位图中),故应包含JS引擎和事件触发线程。
合并线程(Compositor Thread)利用GPU来绘制位图到屏幕以及对位图的显示进行计算判断,故应包含GUI渲染线程。
一个简单的滑动例子便能说明其中的区别。
用户在滑动时,合并线程将直接利用GPU对之前的位图进行计算和展示,并不需要主线程的参与,而若是绑定了事件处理器,则会如下图所示。
主线程会参与执行对应的事件处理函数,合并线程会等待函数执行完成后再继续工作,显示设备刷新频率一般在50~60,那么60fps是能够得到最佳的动画显示,也就是说处理函数必须是在 1000 / 60 = 16.67ms之内完成,否则便会出现卡顿抖动的现象。
渲染-层模型
在Chrome中实际上有几种不同类型的层:掌管DOM子树的渲染层(RenderLayer)以及掌管渲染层子树的图形层(GraphicsLayer)。 我们这里最关心的是后者,因为作为纹理上传到GPU之中的就是图形层。
浏览器的渲染过程如下图所示:
其中的layout和paint是在每一个图层上进行的,当有一个元素经常变化,为了减少这个元素对页面的影响,我们可以为这个元素创建一个单独的图层,在浏览器paint位图会对图层进行分开渲染,最终再组合到一起,这样便不需要绘制整个网页,从而提高页面的渲染性能。
其概念分为了:
RenderLayer
GraphicsLayer
这篇文章详细的讲述了浏览器层模型这个概念。简单来说,一张页面由渲染层(RenderLayer)和图形层(GraphicsLayer)构成,这将便于主线程绘制位图,浏览器会认为图形层所占的区域不会经常变化导致回流(reflow)。图形层通常将交由GPU来进行绘制计算,如旋转、位移等。
因此对于图形层来说,主线程只需要绘制一次位图,之后便不会在做任何布局、绘制和提交位图给GPU。
回到最初的问题上,展开和收缩之所以会导致卡顿,根本原因便在于该dom元素属于渲染层(Render Layer),在高度变化的过程中,主线程在不断的绘制位图,再交由合并线程绘制到屏幕,其流畅程度便取决于主线程的计算速度和合并线程读取位图的速度,如下图所示。
这里写了一个简单的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
可以看到在Main中,不断的进行着位图绘制和合并层操作,这在dom节点多的页面中,高度的变化导致的回流(reflow)计算会更加的耗费性能。
一般的展开和收缩,可以利用 transform:scale(1)
属性,拥有该属性的元素都会被认为是图形层(GraphicsLayer)。
效果很明显,Main只进行了一次位图paint,剩下的便是GPU在进行动画绘制。
GPU善于计算位图的2d和3d转化,如拉伸、压缩、位移、旋转等,这些位图(也就是图像层)往往不会导致其他元素位置变动,触发回流,同时也不会触发本身元素的重绘(repaint),简单来说,一切变化都在GPU中进行,这使得页面往往能够展示一些非常炫酷的动画效果,比如这个3D 行星运转,而这些效果的流畅度纯粹取决于GPU。
硬件加速
硬件加速是基于层模型的一种浏览器优化策略,利用GPU对纹理(也就是图层或位图)生成快照存储起来,不会在每一帧的渲染中都去通知主线程重新生成。
优雅降级
在本次场景中,所需要的并不是对元素的拉伸效果,而是对内容的展示,其效果肯定会影响后面的元素布局,触发回流,考虑到安卓机器良莠不齐,同时对于安卓浏览器,css动画的硬件加速只适用于transform和opacity,因此只能对其进行优雅降级……
参考文章:
Chrome 渲染优化 - 层模型
CSS3 3D 行星运转 && 浏览器渲染原理
深入浏览器理解CSS animations 和 transitions的性能问题
深度剖析浏览器渲染性能原理,你到底知道多少?
GPU:合成加速
渲染性能
页面性能优化实践总结
吸顶
position:sticky
sticky是css3出的新特性,能够在滑动时,根据设置的top或bottom值,来自动切换成fixed或relative状态。
当在目标区域可见时,其表现为relative,当滑动超出屏幕时,表现为fixed,可以说是浏览器专门为吸顶这类功能而设计的,但在兼容方面有很大的问题,故只有换种思路。
监听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模拟滚动
经由查阅资料,总结出两种解决方案:
使用native
使用js模拟滑动
自然而然的,尝试使用第二种方案,引入ISCroll库来模拟惯性滚动,该库使用transform属性对元素进行2d位移,开启图形层,避免了reflow,使用GPU来计算和渲染,能够大大的提高性能,最终在安卓上的流畅效果也证明了这点。
requestAnimateFrame
由于IScroll的滚动容器大小是固定的,在展开或收缩时,需要手动调用refresh来实时的刷新高度。
起初,我设置了interval来调用refresh,但即使在ios中,也有明显的抖动,即便我将interval设置到了最小的时间10ms,仍然会有这种现象。
显然,因为js的执行时间是不稳定的,interval有时在一幁里调用了多次,而有时又没有被调用,这导致其有时会丢失帧,同时,interval也会产生很多重复的计算,损耗大量性能。
最终,采用了requestAnimateFrame来取代interval,因为该函数会在每一帧开始时调用回调函数,这样就可以保证每一帧都能够只执行一次refresh了,当然前提是执行时间要控制在3~4ms内。
参考链接:
- 渲染性能原理
- 移动端吸顶、动画、浏览器层模型以及相关总结
- 总结AJAX相关JS代码片段和浏览器模型。
- 总结AJAX相关JS代码片段和浏览器模型
- 总结AJAX相关JS代码片段和浏览器模型
- 总结AJAX相关JS代码片段和浏览器模型
- 总结AJAX相关JS代码片段和浏览器模型
- OSI 模型层以及各层协议
- unity模型动画资源相关
- 动画相关总结
- OSI七层模型总结
- 盒模型以及相关样式
- 浏览器对象模型BOM总结
- 浏览器对象模型BOM总结
- Animation,动画相关类总结
- 模型层的写作风格以及through
- 控制器层的相关操作以及ajax
- 浏览器兼容性总结以及处理
- 移动端弹出层后一系列问题总结
- Spark之TopN
- NYOJ1237_最大岛屿
- 安卓自定义View介绍使用
- Spring+junit4 实现注解测试原理解析
- Windows 服务状态在启动或停止时卡住的解决办法
- 移动端吸顶、动画、浏览器层模型以及相关总结
- Myeclips导出war错误
- fmdb实现sqlite数据库的增删改查功能(下)
- 京东——分堆
- Spark之BroadCast
- MySQL中varchar 的最大长度
- C语言中 关于const和#define的区别心得
- 疑问+点子+理解
- 【JVM学习】之 ClassLoader