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

·  阅读 4525

本文是 “H5 必知必会” 系列第二篇。我们提到过,所谓 “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 各代屏幕对照图(来源: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(www.w3.org/TR/css-valu…)的定义:

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

那我们也知道:

  • 宽度(width)、高度(height)、间距(maring/padding)支持百分比值,但默认的相对参考值是包含块的宽度;
  • 边框(border)不支持百分值;
  • 边框圆角半径(border-radius)支持百分比值,但水平方向相对参考值是盒子的宽度,垂直方向相对参考值是盒子的高度;
  • 文本大小(font-size)支持百分比值,但相对参考值是父元素的font-size的值;
  • 盒阴影(box-shadow)和文本阴影(text-shadow)不支持百分比值;
  • ……

可见,即使支持使用百分比值的属性,其百分比参考值也都不是我们想要的屏幕宽度!换句话说,要实现屏幕级的适配,必须有一个统一的全局性单位供我们参照。我们有吗?有。

首先,有一个rem。同样,根据 CSS Values and Units Module Level 4(www.w3.org/TR/css-valu…):

(rem)等于根元素(也就是html元素)font-size属性的计算值。

rem归根结底是一个font-size,但却是一个全局性的度量单位,而且其背后还是像素。如果为了适配目的,根据不同屏幕密度动态修改rem的值(比如,document.documentElement.style.fontSize = rem + 'px'),不就可以实现按比例适配了吗?事实上,阿里手机淘宝团队专门为此研发了一个框架:github.com/amfe/lib-fl…。从 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(caniuse.com/#search=vw):

即 iOS 8 + 和 Android 4.4 + 都支持vw,而目前的手机应用通常支持 iOS 9 + 和 Android 5+。所以,实践中使用vw单位完全可行。而这也正是手机淘宝团队今年年初正式转向使用vw单位适配的原因。

那么,现在唯一的问题就是人工进行设计稿的pxvw单位的转换太麻烦。不过,如果你在开发中使用 Webpack 编译打包,那么已经有人开发了 postcss 的插件:postcss-px-to-viewport(www.npmjs.com/package/pos…)。配置好这个插件,你写 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单位实现适配。最后简单介绍了自动实现pxvw单位转换的插件 postcss-px-to-viewport 的配置。

看到这里,读者可能会不由自主地猜测这个系列第三篇的主题是什么?是讨论 H5 如何使用手机或宿主 App 赋予它的特殊能力吗?我也不知道。因为也有可能会跟大家分享多版本下的 H5 部署与运维策略。当然,还有可能会写一写基于某个脚手架快速从头搭建一个 H5 项目以及实现前端工程化。总之,未来有多种可能。其实我也想知道大家都想看哪个主题?如果你有想法,那欢迎留言吧。

分类:
前端
标签: