前端基础知识概述 -- 移动端开发的兼容适配与性能优化

@chokcoco 2019-06-20 05:05:00发表于 chokcoco/iCSS 原理

本文未完成,写作中。。。

前端的一大工作内容就是去兼容页面在不同内核的浏览器,不同的设备,不同的分辨率下的行为,使页面的能正常工作在各种各样的宿主环境当中。

而本文的主题 -- 移动端开发的兼容适配与性能优化,就是希望能从一些常见的移动端开发问题出发,厘清 Web 移动端开发的前前后后,一些技术的发展过程,一些问题的优化手段以及给出一些常见的兼容性问题的解决方案。

什么是响应式设计

首先先聊聊响应式设计,这个与移动端开发有着密切的联系。

响应式设计即是 RWD,Responsive Web Design。

这里百度或者谷歌一下会有各种各样的答案。这里一段摘自知乎上我觉得很棒的一个答案:什么是响应式布局设计?

根据维基百科及其参考文献,理论上,响应式界面能够适应不同的设备。描述响应式界面最著名的一句话就是“Content is like water”,翻译成中文便是“如果将屏幕看作容器,那么内容就像水一样”。

为什么要设计响应式界面

为什么要费神地尝试统一所有设备呢?

  • 即便是PC或Mac用户,有查显示只有一半的人会将浏览器全屏显示,而剩下的一般人使用多大的浏览器,很难预知;
  • 台式机、投影、电视、笔记本、手机、平板、手表、VR……智能设备正在不断增加,“主流设备”的概念正在消失;
  • 屏幕分辨率正飞速发展,同一张图片在不同设备上看起来,大小可能天差地别;
  • 鼠标、触屏、笔、摄像头手势……不可预期的操控方式正在不断出现。

响应式界面的四个层次

  • 同一页面在不同大小和比例上看起来都应该是舒适的;
  • 同一页面在不同分辨率上看起来都应该是合理;
  • 同一页面在不同操作方式(如鼠标和触屏)下,体验应该是统一的;
  • 同一页面在不同类型的设备(手机、平板、电脑)上,交互方式应该是符合习惯的。

响应式界面的基本规则

  • 可伸缩的内容区块:内容区块的在一定程度上能够自动调整,以确保填满整个页面

  • 可自由排布的内容区块:当页面尺寸变动较大时,能够减少/增加排布的列数

  • 适应页面尺寸的边距:到页面尺寸发生更大变化时,区块的边距也应该变化

  • 能够适应比例变化的图片:对于常见的宽度调整,图片在隐去两侧部分时,依旧保持美观可用

  • 能够自动隐藏/部分显示的内容:如在电脑上显示的的大段描述文本,在手机上就只能少量显示或全部隐藏

  • 能自动折叠的导航和菜单:展开还是收起,应该根据页面尺寸来判断

  • 放弃使用像素作为尺寸单位:用dp(对于前端来说,这里可能是rem)尺寸等方法来确保页面在分辨率相差很大的设备上,看起来也能保持一致。同时也要求提供的图片应该比预想的更大,才能适应高分辨率的屏幕

上面一段我觉得已经涵盖了响应式设计的绝大部分,简单总结起来,可以概括为:

  1. 媒体查询,边界断点的规则设定(Media queries && break point)
  2. 内容的可伸缩性效果(Flexibel visuals)
  3. 流式网格布局(Fluid grids)
  4. 主要内容呈现及图片的高质量(Main content and high quality)

响应式 vs. 自适应

响应式设计是 Responsive Web Design(RWD),自适应设计是 Adaptive Web Design(AWD)。经常有人会将两者混为一谈,或者其实根本也区分不了所谓的响应式与自适应。

其实在我写这篇文章的时候,我也无法很好的去区分两者。

RWD 和 AWD 两者都是为了适配各种不同的移动设备,致力于提升用户体验所产生的的技术。核心思想是用技术来使网页适应从小到大(现在到超大)的不同分辨率的屏幕。通常认为,RWD 是 AWD 的子集

RWD:Ethan Marcote 的文章是大家认为 RWD 的起源。他提出的 RWD 方案是通过 HTML 和 CSS 的媒体查询技术,配合流体布局实现。RWD 倾向于只改变元素的外观布局,而不大幅度改变内容。Jeffrey Zeldman 总结说,我们就把 RWD 定义为一切能用来为各种分辨率和设备性能优化视觉体验的技术

AWD:Adaptive Design 是 Aaron Gustafson 的书的标题。他认为 AWD 在包括 RWD 的 CSS 媒体查询技术以外,也要用 Javascript 来操作 HTML 来更适应移动设备的能力。AWD 有可能会针对移动端用户减去内容,减去功能。AWD 可以在服务器端就进行优化,把优化过的内容送到终端上。

一图胜千言。

image

从定义上而言,RWD 是一套代码,适用于所有屏幕。而 AWD 则是多端多套代码。本文不会过多去纠结响应式与自适应区别,我觉得这两者的本质都是致力于适配不同设备,更好地提升用户体验。

Quora - Responsive Design vs. Adaptive Design?
zhihu -- Responsive design 和 Adaptive design 的区别

渐进增强 vs. 优雅降级

  • 渐进增强(progressive enhancement):针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。

  • 优雅降级(graceful degradation):一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

区别:优雅降级是从复杂的现状开始,并试图减少用户体验的供给,而渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要。降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带。 

渐进增强/优雅降级通常是 AWD 会牵扯到的另一个技术术语。本质上而言即是随着屏幕的大小的改变,功能会一点一点增强。

也通常会用在一些高级 CSS3 属性上,我们对一些 CSS 属性进行特性检测,甚至不进行特性检测直接使用。后果是在支持它的网页上该属性正常展示,而不支持它的网页该属性不生效,但也不影响用户的基本使用。

典型的例子是 CSS3 逐渐被大众认可并被使用,PC端页面开始由 IE678 向兼容性更好的IE9+,chrome,firefox浏览器转变的时期。我们可以对页面元素直接使用阴影,圆角等属性。对于不支持它的低版本 IE 而言,没有什么损失,而对于支持它的高级浏览器而言,带给了用户更好的交互视觉体验,这就是渐进增强。

移动端屏幕适配方案

下面会针对一些具体的案例,展开讲讲。第一个是高保真还原设计稿,也就是如何适配移动端繁杂的屏幕大小。

通常而言,设计师只会给出单一分辨率下的设计稿,而我们要做的,就是以这个设计稿为基准,去适配所有不同大小的移动端设备。

在此之前,有一些基础概念需要理解。

一些概念性的东西,大部分人很难一次性记住,或者记了又忘,我觉得记忆这个东西比较看技巧,比如关联法,想象法,把这些生硬的概念与一些符合我们常识的知识关联在一起记忆,往往能够事半功倍。

设备独立像素

以 iPhone6/7/8为例,这里我们打开 Chrome 开发者工具:

image

这里的 375 * 667 表示的是什么呢,表示的是设备独立像素(DIP),也可以理解为 CSS 像素,也称为逻辑像素:

设备独立像素 = CSS 像素 = 逻辑像素

如何记忆呢?这里使用 CSS 像素来记忆,也就是说。我们设定一个宽度为 375px 的 div,刚好可以充满这个设备的一行,配合高度 667px ,则 div 的大小刚好可以充满整个屏幕。

物理像素

OK,那么,什么又是物理像素呢。我们到电商网站购买手机,都会看一看手机的参数,以 JD 上的 iPhone7 为例:

image

可以看到,iPhone7 的分辨率是 1334 x 750,这里描述的就是屏幕实际的物理像素。

物理像素,又称为设备像素。显示屏是由一个个物理像素点组成的,1334 x 750 表示手机分别在垂直和水平上所具有的像素点数。通过控制每个像素点的颜色,就可以使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了,单位为pt。

设备像素 = 物理像素

DPR(Device Pixel Ratio) 设备像素比

OK,有了上面两个概念,就可以顺理成章引出下一个概念。DPR(Device Pixel Ratio) 设备像素比,这个与我们通常说的视网膜屏(多倍屏,Retina屏)有关。

设备像素比描述的是未缩放状态下,物理像素和设备独立像素的初始比例关系。

简单的计算公式:

DPR = 物理像素 / 设备独立像素

我们套用一下上面 iPhone7 的数据(取设备的物理像素宽度与设备独立像素宽度进行计算):

iPhone7’s DPR = iPhone7’s 物理像素宽度 / iPhone7's 设备独立像素宽度 = 2

750 / 375 = 2
或者是 1334 / 667 = 2

可以得到 iPhone7 的 dpr 为 2。也就是我们常说的视网膜屏幕。

视网膜(Retina)屏幕是苹果公司"发明"的一个营销术语。 苹果公司将 dpr > 1 的屏幕称为视网膜屏幕。

image

在视网膜屏幕中,以 dpr = 2 为例,把 4(2x2) 个像素当 1 个像素使用,这样让屏幕看起来更精致,但是元素的大小本身却不会改变:

image

OK,我们再来看看 iPhone XS Max:

屏幕快照 2019-07-08 下午8 02 00

它的物理像素如上图是 2688 x 1242

image

它的 CSS 像素是 896 x 414,很容易得出 iPhone XS Max 的 dpr 为 3。

上面三个概念(CSS像素、设备独立像素、DPR)是我觉得比较重要的,还有一些其他比较重要的概念 PPI、DPI 不影响后续的内容,可以自行去加深理解。

OK,到这里我们就完成了一个小的里程碑。我们通常说的H5手机适配也就是指的这两个维度:

  1. 适配不同屏幕大小,也就是适配不同屏幕下的 CSS 像素
  2. 适配不同像素密度,也就是适配不同屏幕下 dpr 不一致导致的一些问题

适配不同屏幕大小

适配不同屏幕大小,也就是适配不同屏幕下的 CSS 像素。最早移动端屏幕 CSS 像素适配方案是CSS媒体查询。但是无法做到高保真接近 100% 的还原。

适配不同屏幕大小其实只需要遵循一条原则,确保页面元素大小的与屏幕大小保持一定比例。也就是:按比例还原设计稿

屏幕快照 2019-07-29 下午7 23 05

假设我们现在拿到标注为 375*667 的大小的设计稿,其中一个元素的标注如下:

屏幕快照 2019-07-29 下午7 28 13

屏幕快照 2019-07-29 下午7 30 24

以页面宽度为基准的话,那么,

  • 元素的宽度为:209/375 = 55.73%
  • 元素的高度为:80/375 = 21.33%
  • 元素的上左右边距依次计算...

这样,无论屏幕的 CSS 像素宽度是 320px 还是 375px 还是 414px,按照等量百分比还原出来的界面总是正确的。

然而,理想很丰满,现实很骨感。实现上述百分比方案的核心需要一个全局通用的基准单位,让所有百分比展示以它为基准,但是在 CSS 中,根据CSS Values and Units Module Level 4的定义:

百分比值总要相对于另一个量,比如长度。每个允许使用百分比值的属性,同时也要定义百分比值参照的那个量。这个量可以是相同元素的另一个属性的值,也可以是祖先元素的某个属性的值,甚至是格式化上下文的一个度量(比如包含块的宽度)。

具体来说:

  • 宽度(width)、间距(maring/padding)支持百分比值,但默认的相对参考值是包含块的宽度;

  • 高度(height)百分比的大小是相对其父级元素高的大小;

  • 边框(border)不支持百分值;

  • 边框圆角半径(border-radius)支持百分比值,但水平方向相对参考值是盒子的宽度,垂直方向相对参考值是盒子的高度;

  • 文本大小(font-size)支持百分比值,但相对参考值是父元素的font-size的值;

  • 盒阴影(box-shadow)和文本阴影(text-shadow)不支持百分比值;

首先,支持百分比单位的度量属性有其各自的参照基准,其次并非所有度量属性都支持百分比单位。所以我们需要另辟蹊径。

rem

在 vw 方案出来之前,最被大众接受的就是使用 rem 进行适配的方案,因为 rem 满足上面说的,可以是一个全局性的基准单位。

rem(font size of the root element),在 CSS Values and Units Module Level 3中的定义就是, 根据网页的根元素来设置字体大小,和 em(font size of the element)的区别是,em 是根据其父元素的字体大小来设置,而 rem 是根据网页的跟元素(html)来设置字体大小。

flexible

基于此,淘宝早年推行的一套以 rem 为基准的适配方案:lib-flexible。其核心做法在于:

  • 根据设备的 dpr 动态改写 <meta> 标签,设置 viewport 的缩放
  • <html> 元素添加 data-dpr 属性,并且动态改写 data-dpr 的值
  • 根据 document.documentElement.clientWidth 动态修改 <html> 的 font-size ,页面其他元素使用 rem 作为长度单位进行布局,从而实现页面的等比缩放

hotcss

hotcss 不是一个库,也不是一个框架。它是一个移动端布局开发解决方案。使用 hotcss 可以让移动端布局开发更容易。本质的思想与 flexible 完全一致。

vw

严格来说,使用 rem 进行页面适配其实是一种 hack 手段,rem 单位的初衷本身并不是用来进行移动端适配的。至此,我觉得它在移动端适配场景下已经完成了它的历史使命可以光荣退役了,因为到今天我们有了更好的替代方案 vw 。

百分比方案的核心需要一个全局通用的基准单位,而 vw/vh(vmax/vmin) 的出现正好填补了这个空缺。

根据 CSS Values and Units Module Level 4:vw等于初始包含块(html元素)宽度的1%,也就是

  • 1vw 等于 window.innerWidth 的数值的 1%
  • 1vh 等于window.innerHeight 的数值的 1%

以上面图的元素为例,那么,

  • 元素的宽度为:209/375 = 55.73% = 55.73vw
  • 元素的高度为:80/375 = 21.33% = 21.33vw
  • 元素的上左右边距依次计算...

根据相关的测试,可以使用 vw 进行长度单位的有:

  • 容器大小适配,可以使用vw
  • 文本大小的适配,可以使用vw
  • 大于1px的边框、圆角、阴影都可以使用vw
  • 内距和外距,可以使用vw

CodePen Demo(移动端打开):使用 vw 进行页面适配

自动转换插件

postcss-px-to-viewport

vw polyfill

vw 现在毕竟还是存在兼容问题的,看看兼容性:

image

其实已经覆盖了绝大部分设备,那么如果业务使用了且又真的出现了兼容问题,应该怎么处理呢?有两种方式可以进行降级处理:

  • CSS Houdini:通过CSS Houdini针对vw做处理,调用CSS Typed OM Level1 提供的 CSSUnitValue API。
  • CSS Polyfill:通过相应的Polyfill做相应的处理,目前针对于 vw 单位的 Polyfill 主要有:vminpoly、Viewport Units Buggyfill、vunits.js和 Modernizr

更多详情可以读读:

图片适配及优化

图像通常占据了网页上下载资源绝的大部分。优化图像通常可以最大限度地减少从网站下载的字节数以及提高网站性能。

通常可以,有一些通用的优化手段:

  1. 消除多余的图像资源
  2. 尽可能利用 CSS3\SVG 矢量图像替代某些光栅图像
  3. 谨慎使用字体图标,使用网页字体取代在图像中进行文本编码
  4. 选择正确的图片格式
  5. 为不同 DPR 屏幕提供最适合的图片尺寸

本文重点关注如何在不同的 dpr 屏幕下,让图片看起来都不失真。

首先就是上述的第二点,尽可能利用 CSS3\SVG 矢量图像替代某些光栅图像。某些简单的几何图标,可以用 CSS3 快速实现的图形,都应该尽量避免使用光栅图像。这样能够保证它们在任何尺寸下都不会失真。

其次,实在到了必须使用光栅图像的地步,也是有许多方式能保证图像在各种场景下都不失真。

无脑多倍图

在移动端假设我们需要一张 CSS 像素为 300 x 200 的图像,考虑到现在已经有了 dpr = 3 的设备,那么要保证图片在 dpr = 3 的设备下也正常高清展示,我们最大可能需要一张 900 x 600 的原图。

这样,不管设备的 dpr 是否为 3,我们统一都使用 3 倍图。这样即使在 dpr = 1,dpr = 2 的设备上,也能非常好的展示图片。

当然这样并不可取,会造成大量带宽的浪费。现代浏览器,提供了更好的方式,让我们能够根据设备 dpr 的不同,提供不同尺寸的图片。

srcset

简单来说,srcset 可以根据不同的 dpr 拉取对应尺寸的图片:

<div class='illustration'>
  <img src='illustration-small.png'
       srcset='images/illustration-small.png 1x,
               images/illustration-big.png 2x'
       style='max-width: 500px'/>
</div>

srcset 配合 sizes/w

<img 
        sizes = “(min-width: 600px) 600px, 300px" 
        src = "photo.png" 
        srcset = “photo@1x.png 300w,
                       photo@2x.png 600w,
                       photo@3x.png 900w,
                       photo-large@2x.png 1200w" 
>

字体适配方案

字体大小

字体是很多前端开发同学容易忽略的一个点,但是其中也是有很多小知识点。

首先要知道,浏览器有最小字体限制:

  • PC上最小 font-size=12px
  • 手机上最小 font-size=8px

如果小于最小字体,那么字体默认就是最小字体。

其次,很多早期的文章规范都建议不要使用奇数级单位来定义字体大小(如 13px,15px...),容易在一些低端设备上造成字体模糊,出现锯齿。

字体族

对于字体族(font-family)的定义,要了解以下几点:

  • 衬线字体(serif)与非衬线字体(sans-serif),
  • 定义字体时要兼顾中文字体与西文字体,且西文字体在前,中文字体在后
  • 要兼顾多操作系统,知道不同操作系统的预装字体是不一样的
  • 要知道PC端字体的定义与移动端字体定义的策略是不一样的
  • 兼顾旧操作系统,以字体族系列 serif 和 sans-serif 结尾

具体的,可以看看这两篇文章:

前端布局的兼容适配

前端工程师的一大工作内容就是页面布局。无论在PC端还是移动端,页面布局的兼容适配都是重中之重。在整个前端发展的历程中,布局的方法也在不断的推陈出新。

布局发展历程

简单来说,前端的布局发展历程经历了下面几个过程:

表格布局 --> 定位布局 --> 浮动布局 --> flexbox布局 --> gridbox布局

每一种布局在特定时期都发挥了重要的作用,而每一种新的布局方式的出现,往往都是因为现有的布局方式已经在该时期已经无法很好的满足开发者的需求,无法满足越来越潮流的页面布局的方式。

以 Flexbox 的出现为例子,在 Flexbox 被大家广为接受使用之前。我们一直在使用定位+浮动的布局方式。像下面这个布局:

flexbox

容器宽度不定,内部三个元素,均分排列且占满整个空间,并且垂直居中。如果使用定位+浮动的布局方式,你无法很快想到最佳的解决方式。三个元素并排那么必然需要浮动或者绝对定位,容器宽度不定且中间元素始终居中,需要顾虑的方面就很多了。也许使用 text-align: justufy 可以 hack 实现,等等等等。

然而,使用 flexbox 布局的话,只需要:

.container {
    display: flex;
    justify-content: space-between;
    align-items: center;
}

flexbox 的出现,一次性解决了流动布局,弹性布局,排列方式等多个问题。并且它是简洁的,可控的。

再来看一个例子,水平垂直居中一个元素。使用 flexbox 也许是最便捷的:

.container {
    display: flex;
}

.item {
    margin: auto;
}

最便捷的垂直居中方式

CSS Grid Layout

OK,flexbox 已经足够优秀了,为什么 gird 网格布局的出现又是为什么?它解决了什么 flex 布局无法很好解决的问题?

看看下面这张图:

css grid layout

flexbox 是一维布局,他只能在一条直线上放置你的内容区块;而grid是一个二维布局。它除了可以灵活的控制水平方向之外,还能轻易的控制垂直方向的布局模式。对于上图那样的九宫格布局,它就可以轻而易举的完成。

一图以蔽之,flexbox:

flexbox

gridbox:

gridbox

图片截取自陈慧晶老师在 2019 第五届 CSS 大会上的分享 -- 新时代CSS布局

动画与性能

提高 CSS 动画性能的正确姿势

1px线

设计师想要的 retina 下 border: 1px,其实是 1 物理像素宽,而不是 1 CSS 像素宽度,对于 CSS 而言:

  • 在 dpr = 1 时,此时 1 物理像素等于 1 CSS 像素宽度;
  • 在 dpr = 2 时,此时 1 物理像素等于 0.5 CSS 宽度像素,可以认为 border-width: 1px 这里的 1px 其实是 1 CSS像素宽度,等于 2 像素物理宽度,设计师其实想要的是 border-width: 0.5px;
  • 在 dpr = 3 时,此时 1 物理像素等于 0.33 CSS 宽度像素,设计师其实想要的是 border: 0.333px

然而,并不是所有手机浏览器都能识别 border-width: 0.5px,在 iOS7 以下,Android 等其他系统里,小于 1px 的单位会被当成为 0px 处理,那么如何实现这 0.5px、0.33px 呢?

这里介绍几种方法:

  • 渐变实现
  • 使用缩放实现
  • 使用图片实现(base64)
  • 使用SVG实现(嵌入 background url)

Retina 屏幕下 1px 线的实现