H5必知必会之像素级还原设计稿

2,569 阅读14分钟
原文链接: mp.weixin.qq.com

奇技指南

本文是“H5必知必会”系列第二篇,今天要讨论的是,H5在构造页面布局时,如何逼真、像素级还原设计稿。

本文来自公众号奇舞周刊,作者李松峰,资深技术图书译者,翻译出版过40余部技术及交互设计专著,现任360奇舞团高级前端开发工程师,360前端技术委员会委员、W3C AC代表

艾瑞抱得,开工快乐鸭!!!

 前 言 

本文是“H5必知必会”系列第二篇。在第一篇“H5必知必会之与App交互”中我们提到过,所谓“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像素(如image@1x.jpg),那么适配高密度屏的版本则分别是1500x900像素(如image@3x.jpg)和1000x600像素(如image@2x.jpg)。这样才能做到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里使用百分比单位吗?很可惜,不能

我们知道,根据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

这一节实际是没有必要的。不过,为了完整起见,我们就简单说一下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项目以及实现前端工程化。总之,未来有多种可能~

今天是开心的周末~

明天也要加油鸭!

界世的你当不

只做你的肩膀

 360官方技术公众号 

技术干货|一手资讯|精彩活动

空·