移动端吸顶、动画、浏览器层模型实战分析

  1. 云栖社区>
  2. 博客列表>
  3. 正文

移动端吸顶、动画、浏览器层模型实战分析

烽穹寒渊 2018-07-09 19:02:59 浏览511 评论0

摘要: 问题归纳 顶部通知公告条自动吸顶判断 点击自动展开动画,兼容Android与ios 页面中的图标与文字对齐(垂直居中对齐、水平居中对齐) 移动端居中、间距自适配 h5页面自定义native顶部导航条 知识点归纳 惯性滑动的事件触发机制 iscroll模拟滚动,滚动事件在安卓中未被触发 reques.

问题归纳

  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的通信

展开收缩

d0e56af442cb0d8d111457b2e13a582b

在移动端要求实现点击后,展开或收缩内容区域,承载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渲染线程。

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

image

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

image

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

渲染-层模型

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

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

image

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

其概念分为了:

  1. RenderLayer
  2. GraphicsLayer

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

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

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

image

这里写了一个简单的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

image

image

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

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

image

image

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

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

硬件加速

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

优雅降级

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

参考文章:

吸顶

b1a775dda88ddc97c995cbe272c94edf

position:sticky

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

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

image

监听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,仍然会有这种现象。

image

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

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

参考链接:

【云栖快讯】阿里云栖开发者沙龙(Java技术专场)火热来袭!快来报名参与吧!  详情请点击

网友评论

烽穹寒渊
文章5篇 | 关注0
关注
阿里云推出的一款移动App数据统计分析产品,为开发者提供一站式数据化运营服务 查看详情
阿里云移动APP解决方案,助力开发者轻松应对移动app中随时可能出现的用户数量的爆发式增长、... 查看详情
基于大数据的移动云服务。帮助App快速集成移动推送的功能,在实现高效、精确、实时的移动推送的... 查看详情
为您提供简单高效、处理能力可弹性伸缩的计算服务,帮助您快速构建更稳定、安全的应用,提升运维效... 查看详情
双12

双12