移动端开发效率指南

686 阅读8分钟

页面滚动不流畅?

iOS系统设备滚动卡顿

iOS系统设备体验卡顿?手指离开滚动元素,立即停止滚动,表现不流畅,没有惯性?

原因是 iOS 5.0 及之后版本,滚动定义了两个值:auto 和 touch,默认为 auto,如想启用惯性滚动,需给滚动容器设置-webkit-overflow-scrolling属性值为touch

-webkit-overflow-scrolling: touch; /* 当手指从触摸屏上移开,会保持一段时间的滚动 */

-webkit-overflow-scrolling: auto; /* 当手指从触摸屏上移开,滚动会立即停止 */

平滑的锚点定位滚动

scrollIntoView()方法会滚动元素的父容器,使被调用scrollIntoView()的元素对用户可见。

target.scrollIntoView({
    block: 'start', // 定义垂直方向
    behavior: 'smooth', // 定义动画过渡效果
    inline: 'nearest' // 定义水平方向
});

图片显示模糊?

绘制的图片显示模糊?

物理像素

设备屏幕实际拥有的像素点,屏幕的基本单元,是有实体的。如iPhone 6的屏幕在宽度方向有750个像素点,高度方向有1334个像素点,所有iPhone 6 总共有750*1334个像素点。

逻辑像素

也叫“设备独立像素”(Device-independent pixel),可理解为反映在CSS/JS程序里面的像素点,CSS像素是逻辑像素的一种。

我们平时描述一张图片宽高时一般用 200px * 100px,这里的px也是逻辑像素。

设备像素比

一个设备的物理像素与逻辑像素之比(Device Pixel Ratio,DPR)

比如在Retina屏上,DPR不再是1,比如2(iPhone 5 6 7 8)或3(iPhone 6 Plus等一系列Plus)或为非整数(一些Android机)。如果还按照DPR=1进行展示,那么同一张图片在高清屏上显示的区域将是非高清屏的几分之一,定会导致模糊的问题。

如何解决?

以iPhone 6为例,物理像素是750 * 1334,逻辑像素可通过screen.width和screen.height获取,是375 * 667,因此DPR=2,可以直接通window.devicePixelRatio来获取。

熟悉了这些基础知识,使用canvas绘制图片展示在移动设备上时,在设置图片尺寸、元素位移、处理文字时,用逻辑像素(CSS像素) * DPR即可解决清晰度的问题。

ctx.font = `${14 * dpr}px PingFangSC-Regular`; // 字体字号
 
ctx.drawImage( // 图片尺寸/位置
    code,
    200 * dpr, 
    200 * dpr,
    30 * dpr,
    30 * dpr,
);

展示的图片显示模糊?

可以使用两个属性:srcset 和 size来提供更多额外的资源图像和提示,帮助浏览器选择正确的资源。

srcset定义了允许浏览器选择的图像集,以及每个图像的大小。

sizes定义了一组媒体条件(例如屏幕宽度)并且指明当某些媒体条件为真时,什么样的图片尺寸是最佳选择。

<img 
    srcset="elva-fairy-320w.jpg 320w,
             elva-fairy-480w.jpg 480w,
             elva-fairy-800w.jpg 800w"
     sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px"
     src="elva-fairy-800w.jpg" 
     alt="Elva dressed as a fairy"
>

<img 
    srcset="elva-fairy-320w.jpg,
             elva-fairy-480w.jpg 1.5x,
             elva-fairy-640w.jpg 2x"
     src="elva-fairy-640w.jpg" 
     alt="Elva dressed as a fairy"
>

<picture>
  <source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg">
  <source media="(min-width: 800px)" srcset="elva-800w.jpg">
  <img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva">
</picture>

未来也可以通过Navigator.connection判断当前网络环境,选择展示高清晰度内容或低清晰度的图片,但目前还属于实验性的属性,兼容性极差

动效影响性能,硬件加速搞起来?

我们首先来看看,一个页面的展示经历了几个步骤:

  1. JavaScript:通过 JS 实现视觉变化,做一个动画或是往页面里添加 DOM 元素。

  2. Style:根据 CSS 选择器,对每个 DOM 元素匹配对应的 CSS 样式。

  3. Layout:计算每个 DOM 元素在屏幕上显示的大小和位置。由于页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。

  4. Paint:绘制文字、颜色、图像、边框和阴影等,是一个 DOM 元素所有的可视效果。绘制过程是在多个层上完成的。

  5. Composite:在每个层上完成绘制之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。

再来了解下浏览器的渲染流程:

那么 RenderObject 如何才能拥有独立的 RenderLayer?需满足如下条件,不满足条件的 RenderObject 则和其第一个拥有 RenderLayer 的父元素共用一个:

  • NormalPaintLayer

  • 根元素(HTML)

  • 有明确的定位属性(relative、fixed、sticky、absolute)

  • 透明的(opacity 小于 1)

  • 有 CSS 滤镜(fliter)

  • 有 CSS mask 属性

  • 有 CSS mix-blend-mode 属性(不为 normal)

  • 有 CSS transform 属性(不为 none)

  • backface-visibility 属性为 hidden

  • 有 CSS reflection 属性

  • 有 CSS column-count 属性(不为 auto)或者 有 CSS column-width 属性(不为 auto)

  • 当前有对于 opacity、transform、fliter、backdrop-filter 应用动画

  • OverflowClipPaintLayer

  • overflow 不为 visible

  • NoPaintLayer

  • 不需要 paint 的 PaintLayer,比如一个没有视觉属性(背景、颜色、阴影等)的空 div

流程图中提到 “某些特殊的 RenderLayer 会被认为是 CompositingLayers,合成层拥有单独的 GraphicsLayer,而其他不是合成层的渲染层,则和其第一个拥有图形层的父层共用一个”,那么渲染层如何才能被提升为合成层? 首先 RenderLayer 提升为 CompositingLayers 有一个先决条件,该渲染层必须是 NormalPaintLayer 提升为合成层又有什么好处?

  • CompositingLayers 的位图会交由 GPU 合成,比 CPU 处理要快

  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层

  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

既然利用合成层对于提升页面性能有很大帮助,如何实现呢? 可以利用 will-change 属性,will-change 为web开发者提供了一种告知浏览器该元素会有哪些变化的方法,这样浏览器可以在元素属性真正发生变化之前提前做好对应的优化准备工作。 这种优化可以将一部分复杂的计算工作提前准备好,使页面的反应更为快速灵敏。

.target {
  will-change: transform;
}

对于那些目前还不支持 will-change 属性的浏览器,目前常用的是使用 3D transform 属性来强制提升为合成层:

.target {
  transform: translateZ(0);
}

或是未来,可以通过 OffscreenCanvas 创建一个脱离屏幕渲染的canvas对象,目前兼容性还达不到能够使用的标准。

但需要注意的是,不要创建太多的渲染层。因为每创建一个新的渲染层,就意味着新的内存分配和更复杂的层的管理。

如果已经把一个元素放到一个新的合成层里,那么可以使用 Timeline 来确认这么做是否真的改进了渲染性能。别盲目提升合成层,一定要分析其实际性能表现。

总结一下

之前我们都很喜欢使用 translateZ(0) 来进行所谓的硬件加速,以提升性能,但是性能优化并没有所谓的“银弹”。抛开了对页面的具体分析,任何的性能优化都是站不住脚的,盲目的使用一些优化措施,结果可能会适得其反。因此切实的去分析页面的实际性能表现,不断的改进测试,才是正确的优化途径。

如果想更深入理解渲染原理,请移步这里:

www.chromium.org/developers/…

developers.google.com/web/fundame…

刘海屏如何适配?

body {
  padding:
    env(safe-area-inset-top, 20px)
    env(safe-area-inset-right, 20px)
    env(safe-area-inset-bottom, 20px)
    env(safe-area-inset-left, 20px);
}

safe-area-inset-*由四个定义了视口边缘内矩形的 top, right, bottom 和 left 的环境变量组成,这样可以安全地放入内容,而不会有被非矩形的显示切断的风险。对于矩形视口,例如普通的笔记本电脑显示器,其值等于零。 对于非矩形显示器(如圆形表盘,iPhoneX屏幕),在用户代理设置的四个值形成的矩形内,所有内容均可见。

为了告诉浏览器使用屏幕上所有的可用空间,并以此使用env()变量,需要添加一个新的视口元值:

<meta name="viewport" content="... viewport-fit=cover">

呈现效果:

一些因兼容性,更适用于移动端的API了解一下?

Number.isSafeInteger() 方法用来判断传入的参数值是否是一个“安全整数”(safe integer)

Number.prototype.toLocaleString() 方法返回这个数字在特定语言环境下的表示字符串

比如处理千分位:

var number = 3500;

console.log(number.toLocaleString()); // Displays "3,500" if in U.S. English locale 

direction 用来设置文本、表列水平溢出的方向。 rtl 表示从右到左 (类似希伯来语或阿拉伯语), ltr 表示从左到右 (类似英语等大部分语言)

不处理数据,通过样式实现倒序:

开个脑洞,下列需求场景,你会考虑如何实现?

拆解实现步骤

用图的部分:

通过 CSS linear-gradient 绘制网格线:

图表怎么体现语义化?

真实数据是?

图表是如何实现的?

柱状图表示区间

折线图

然后翻转坐标系