本文是“H5必知必会”系列第二篇。在第一篇“H5必知必会之与App交互”(https://mp.weixin.qq.com/s/gIKXrSDgg97g2j-fFK8hAA)中我们提到过,所谓“H5”本来应该是HTML5的简称。但是,在中国,“H5”并不是HTML5的简称那么简单,它更多地被用于指代内嵌在手机App中的网页。无论国外有没有“H5”这个说法(应该没有),反正在国内只要说到App内嵌的网页或网页应用,你完全可以说它是“H5”。一说“H5”,产品经理、设计师、Android/iOS应用开发者、后端工程师全都明白。
不能不说,这个现象很神奇。也许,在前端行业之外人的眼里,手机屏幕上的网页就应该有一个跟大屏幕上的网页不一样的名字,这样才好区分。事实上,就算是在前端开发行业内,有这么一个词让我们瞬间就可以定位到一种应用形态,至少也是一件非常便利的好事。
那么手机上的网页跟大屏幕上的网页有什么不同呢?
首先,手机上的网页运行在手机浏览器或者App内嵌的WebView中,可以使用手机特有的硬件能力,比如GPS、加速计、陀螺仪等传感器;此外,如果加载了宿主App的JSSDK,还可以访问App暴露给网页的各种能力,比如获取登录用户的信息、拍照上传和分享,等等。
其次,手机上的网页布局设计要服从于原生App的界面(UI)设计规范,比如谷歌的扁平化设计Material Design,苹果的人机界面指南(Human Interface Guide)等;总之,手机上的网页看起来必须像原生App的界面,包括使用与原生App一致的布局组件、字体图标、配色方案,乃至交互模式等,比如要支持触摸而非鼠标点击交互。
最后,手机上的网页,特别是运行于App内的网页,构成混合移动应用的一部分,通常都需要与原生App互操作;需要H5与原生应用双方共同商定基础接口,并针对特定的交互场景确定具体的交互模式,包括单向调用还是双向调用,同步还是异步,如何传递数据和凭据,等等。
上述第三点,正是本系列第一篇文章“H5必知必会之一App交互”讨论的内容,感兴趣的同学稍后可以参考。那么今天,本文要讨论的则是上述第二点,即H5在构造页面布局时,如何逼真、像素级还原设计稿。
要说还原,必须先从适配说起。
H5适配手机主要有两个维度:
- 适配不同像素密度
- 适配不同屏幕大小
像素密度,顾名思义就是CSS中的1像素对应多少物理像素。我们以这张iPhone各代屏幕对照图(来源:https://www.paintcodeapp.com/news)为例:
先看iPhone Xs Max和iPone XR。前者屏幕用CSS像素度量是414x896,而用实际渲染的物理像素度量则是1242x2688像素,简单换算可知,1242/414 = 3(或2688/896 = 3)。换句话说,iPhone Xs Max是人们常说的3倍屏(或@3x),即每个CSS像素对应9个物理像素(因为宽高均为3像素)。而iPhone XR呢,则是人们常说的2倍屏(或@2x),每个CSS像素对应4个物理像素。
当然还有1倍屏。上图最后一个iPhone 2G/3G/3GS,就是1倍屏,即每个CSS像素对应1个物理像素。像这种估计市面上已经绝迹的初代iPhone是不用适配的,因为1个CSS像素就对应屏幕上的1个物理像素。(当然,笔记本和电脑外接的大屏幕至今都是1倍屏,所以目前还不需要我们适配像素密度。)
我们常说的适配像素密度,通常指图片如何在3倍屏和2倍屏上显示不失真。当然,适配原则非常简单:1个图片像素对应1个物理像素,图片就不会失真。具体来说,假设原始图片是500x300像素(如[email protected]),那么适配高密度屏的版本则分别是1500x900像素(如[email protected])和1000x600像素(如[email protected])。这样才能做到1个物理像素对应到1个图片像素。
当然,也有一个简单粗暴的适配方案,就是针对所有屏幕,都只提供最高清图片。虽然低密度屏幕用不到那么多图片像素,而且会因为下载多余的像素造成带宽浪费和下载延迟,但从结果上说能保证图片在所有屏幕上都不会失真。
适配像素密度的具体技术方案本文就忽略了,因为它不是本文的重点。本文的重点其实是适配不同屏幕大小。适配不同屏幕大小的原则非常简单:确保页面布局的度量与屏幕大小保持一定比例。
听起来简单,做起来可不容易。因为这里就涉及还设计稿的问题了。比如,设计稿宽度通常是750像素:
在这个宽度下,不同页面组件都有各自的度量值,比如,“对音箱说”这个标题的度量为112x28像素:
而标题的上、右、下、左,距离当前组件的边界,分别是38像素、289像素、394像素和289像素:
当然,每个组件与屏幕边界、组件与组件之间,乃至文本大小、间距,在这个设计稿中也都有确定的像素值度量。那么问题来了,面对设计稿中林林总总的像素值,我们如何在H5页面中还原它们?
答案很简单:按比例还原。
仍然以前面的设计图为例,我们知道设计稿假定的屏幕宽度为750像素(实际上多少像素都没问题,因为我们是按比例还原设计稿),而如下面几张图所示,这个圆角矩形组件的宽度是690像素:
它与上方距离为30像素(与左、右边界同样是30像素):
有了这些像素数据,经过简单的数学换算,不难得出:
- 组件宽度占屏幕宽度的百分比为:690/750 = 0.92,即92%;
- 组件上、右、左边距占屏幕宽度的百分比为:30/750 = 0.04,即4%。
好了,现在只要能让浏览器(WebView)按照这个比例去渲染,我们的组件就能做到对设计稿的像素级还原——在750像素宽的屏幕上,组件及其子组件的所有像素度量都能严格与设计稿保持一致!
更重要的是,在屏幕不是750像素的情况下(事实上750像素宽的手机屏幕至今还没出现过,注意我是CSS像素宽度),页面的布局依旧可以按照设计稿的既定比例完美适配,即在大一点的屏幕上,页面的方方面面都会显示得大一些;而在小一点的屏幕上,则整个布局都会按比例缩小一些。
事实上,除了宽度、高度和间距,文本大小(即font-size
)、边框、阴影等度量也可以按这个思路把像素转换为百分比。
好了,以上就是关于适配和还原的概念阐述。接下来,我们从理念到实践,看一看技术上如何实现吧。
上一节概念阐述提到按比例适配可以做到像素级还原设计稿。但是,实践中可以直接在CSS里使用百分比单位吗?很可惜,不能。
我们知道,根据CSS Values and Units Module Level 4(https://www.w3.org/TR/css-values-4/#percentages)的定义:
百分比值总要相对于另一个量,比如长度。每个允许使用百分比值的属性,同时也要定义百分比值参照的那个量。这个量可以是相同元素的另一个属性的值,也可以是祖先元素的某个属性的值,甚至是格式化上下文的一个度量(比如包含块的宽度)。
那我们也知道:
- 宽度(
width
)、高度(height
)、间距(maring
/padding
)支持百分比值,但默认的相对参考值是包含块的宽度; - 边框(
border
)不支持百分值; - 边框圆角半径(
border-radius
)支持百分比值,但水平方向相对参考值是盒子的宽度,垂直方向相对参考值是盒子的高度; - 文本大小(
font-size
)支持百分比值,但相对参考值是父元素的font-size
的值; - 盒阴影(
box-shadow
)和文本阴影(text-shadow
)不支持百分比值; - ……
可见,即使支持使用百分比值的属性,其百分比参考值也都不是我们想要的屏幕宽度!换句话说,要实现屏幕级的适配,必须有一个统一的全局性单位供我们参照。我们有吗?有。
首先,有一个rem
。同样,根据CSS Values and Units Module Level 4(https://www.w3.org/TR/css-values-4/#font-relative-lengths):
(rem)等于根元素(也就是
html
元素)font-size
属性的计算值。
rem
归根结底是一个font-size
,但却是一个全局性的度量单位,而且其背后还是像素。如果为了适配目的,根据不同屏幕密度动态修改rem
的值(比如,document.documentElement.style.fontSize = rem + 'px'
),不就可以实现按比例适配了吗?事实上,阿里手机淘宝团队专门为此研发了一个框架:https://github.com/amfe/lib-flexible。从Github仓库来看,这个框架用了大概有两年多时间,2019年年初正式宣布退役。因为又找到了新的更好的全局性参照单位:vw
。
什么是vw
?同样根据CSS Values and Units Module Level 4:
(vw)等于初始包含块(html元素)宽度的1%。
换句话说,可以认为:1vw
就等于屏幕宽度的1%
。哇,这个单位似乎是专门为我们适配量身打造的。首先,它本质上就是一个百分比单位,比如3vw
就相当“屏幕宽度的3个百分点”;其次,它又是全局性的与屏幕宽度直接相关的单位。那么上一节概念阐述中举的两个百分比的例子,用vw
单位可以直接写成:
- 组件宽度占屏幕宽度的百分比为:690/750 = 0.92,即
92vw
; - 组件上、右、左边距占屏幕宽度的百分比为:30/750 = 0.04,即
4vw
。
那么移动浏览器对vw
的支持度如何呢?根据caniuse.com(https://caniuse.com/#search=vw):
即iOS 8+和Android 4.4+都支持vw
,而目前的手机应用通常支持iOS 9+和Android 5+。所以,实践中使用vw
单位完全可行。而这也正是手机淘宝团队今年年初正式转向使用vw
单位适配的原因。
那么,现在唯一的问题就是人工进行设计稿的px
到vw
单位的转换太麻烦。不过,如果你在开发中使用Webpack编译打包,那么已经有人开发了postcss的插件:postcss-px-to-viewport(https://www.npmjs.com/package/postcss-px-to-viewport)。配置好这个插件,你写CSS的时候可以严格按照设计稿上的像素值去写,这个插件负责将你写的px
转换为vw
。这样一来,我们所说的“像素级还原设计稿”,就彻底打通了开发和呈现,真的做到了“像素级”!
(假如,我只是说假如你没有或不能使用Webpack打包,那么你可能还需要人工计算。)
这一节实际是没有必要的。不过,为了完整起见,我们就简单说一下postcss-px-to-viewport插件的配置。以下内容截取自.postcssrc.js配置文件:
module.exports = {
"plugins": {
// ...
"postcss-px-to-viewport": {
viewportWidth: 750,
viewportHeight: 1334,
unitPrecision: 3,
viewportUnit: 'vw',
selectorBlackList: ['.usepixel'],
minPixelValue: 1,
mediaQuery: false
},
// ...
}
}
其中几个配置项的含义如下:
- viewportWidth:视口宽度,这里设置为跟设计稿宽度一致;
- viewportHeight:视口高度,随便设置一个就可以;
- unitPrecision:转换后值的精度,3表示保留3位小数;
- viewportUnit:转换成什么视口单位,这里当然是
vw
; - selectorBlackList:是一个选择符数组,对应声明中的像素单位不会转换;
- minPixelValue:最小像素值,大于等于这个值才会转换;
- mediaQuery:是否转换媒体查询中的像素。
最后,我们以一个实例展示结束本篇文章。仍然是本文开头的设计稿,还是那个圆角矩形组件,在我们这个应用里的类名是.card
。下面是它的CSS源代码:
section.card {
margin: 0 auto;
margin-bottom: 20px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 6px 6px 0 rgba(0,0,0,0.02);
text-align: center;
padding: 38px 70px 46px;
font-weight: 300;
}
没错,所有这些px
值都从设计稿中实测得到的(事实上是标注页面自动给出的)。而经过postcss-px-to-viewport插件转换之后,可以看到这些值都被转换成了相对于视口宽度的vw
单位:
作为“H5必知必会”系列的第二篇,本文主要从两个方面讨论了如何高保真还原设计稿,同时适配形态各异的手机屏幕。首先,我们从理论或概念上先搞清楚了适配和还原的内容和目。H5适配主要分适配不同像素密度和适配不同屏幕大小。而适配的目的是为保证用户体验,比如图片不失真;以及在不同大小的屏幕上呈现比例相同的页面布局,这也就是所谓的百分之百还原设计稿,或像素级还原设计稿。
明确了适配和还原的含义及原理,接下来技术实现层面的关键是找到一个全局性的CSS单位。我们先介绍了阿里手机淘宝团队基于rem
的适配框架(这个框架除了动态修改rem
,还解决了真1像素边框的问题),然后介绍了基于更普适的vw
单位实现适配。最后简单介绍了自动实现px
到vw
单位转换的插件postcss-px-to-viewport的配置。
看到这里,读者可能会不由自主地猜测这个系列第三篇的主题是什么?是讨论H5如何使用手机或宿主App赋予它的特殊能力吗?我也不知道。因为也有可能会跟大家分享多版本下的H5部署与运维策略。当然,还有可能会写一写基于某个脚手架快速从头搭建一个H5项目以及实现前端工程化。总之,未来有多种可能。其实我也想知道大家都想看哪个主题?如果你有想法,那欢迎留言吧。