背景:
曾几何时,前端的工作重心一直是适配各类浏览器,每一个前端心中都有一个噩梦叫做,如果这个噩梦可以做一个连续剧的话,那就是:
IE6
。这个系列的浏览器折磨着一代又一代前端,人人无不谈之而色变。就在这个时候有个英雄(
IE6-8
)出现了,凭借着卓越的性能和优秀的用户体验迅速征服了广大用户和饱经苦难的开发同学,大有一统江湖之势。本以为世间就能从此皆美好,谁知这仅仅是开启了另一个世界。接下来就此话题,谈谈移动端的那些事儿。
chrome
1、前世
移动端从狭义讲就是手机。随着移动互联网的兴起,智能手机成了开发者必须重视的领域,甚至在某些大厂出现了只有APP而无PC端应用的现象。对前端而言,移动端摆脱了PC端各类坑爹浏览器,在一定程度来说确实释放了不小的压力,但是随之而来的引入了一个以前不常见的问题----分辨率适配。在前些年安卓严重碎片化,分辨率适配就成了该平台的专利,后来苹果多机型战略横空出世,分辨率适配就成了两个平台共通点。通过下图可以领略一下现状的残酷
以上图片来自于:谷歌开源社区
1.1 适配之初体验
从PC初入移动端,一贯的手法还是把手机当做尺寸更小的电脑来处理。这便催生出了当初盛行一时的“响应式”布局,其试图用一套代码通吃PC和移动端,这种布局方式实现可以分为以下两个步骤:
设置META
同PC端相比,移动设备尺寸要小得多。这就意味着要想将PC上的内容放到手机端显示,势必会造成内容的挤压变形导致无法观看,这个时候就需要有一个机制能让移动端设备完美显示我们想要的内容来,这就是viewport。根据PPK大神的总结,viewpoint并不是一个东西。实际上是三个:和
visual viewport(视觉视窗)
以及
layout viewport(布局视窗)
ideal viewport(理想视窗)
参考地址:https://www.quirksmode.org/mobile/viewports2.html
layout viewport(布局视窗)
引用George Cummins大神在Stack Overflow上的一句话通过谷歌翻译过来大概是这样的,有兴趣的可以看原文:
将布局视窗想象成一个不会改变大小或形状的大图像。现在,您具有一个较小的框架,可以通过该框架查看较大的图像。小框架周围环绕着不透明的材料,遮挡了大图像的一部分,但您看不到全部。您可以通过框看到的大图像部分是可视视窗。您可以在按住框架(缩小)的同时退回大图像,以一次查看整个图像,也可以移近(放大)以仅查看一部分。您也可以更改框架的方向,但是大图像(布局视窗)的大小和形状不会改变。当然布局视窗的大小在不同平台也是有定义的: Safari iPhone使用980px,Opera 850px,Android WebKit 800px和IE 974px
visual viewport(视觉视窗)
前面提到布局视窗是一个不改变大小的大图像,是一个我们不可见的图像。而我们要想看到内容就需要一个窗口偷显示,这就是视觉视窗。视觉视窗就好比是一个剪影,通过这个剪影透出布局视窗上的内容,用户可以通过滚动缩放看到自己想看的内容。这就好比你坐在一间屋子里,屋子的墙上有一扇窗。外面广阔的天地与你无关,你唯一能见到的世界就是那扇窗透进来的风景。而这扇窗就是我们的视觉视窗,外面广阔的天地就是布局视窗。
ideal viewport(理想视窗)
理想视窗可以理解为最适合手机显示的视窗,何为最合适呢?那就是在在任何分辨率下都不需要滚动条,不需要缩放,里面的任何元素都能很好的显示出来。这就是所谓的理想视窗,实际上理想视窗的尺寸也是不固定的,下面就列举几个常见的,具体参数请见PPK大神的原文:https://www.quirksmode.org/mobile/metaviewport/devices.html
以上图片来自于:PPK大神的博客
关于最核心的内容介绍完,剩下的设置就是小儿科了,下面代码就是告诉浏览器使用viewpoint渲染内容,其中宽度为设备宽度(
device-width
),初试缩放等级和最大缩放等级都为1,也就是不允许缩放。
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
CSS3媒体查询
通过CSS3的媒体查询,以几个主流分辨率为分界。将适配范围划分为几个区间,每个主流分辨率则为一个区间,再针对每个区间写特定样式来适配。
@media (min-width: 1200px) { 适配样式写这里 }
@media (min-width: 768px) and (max-width: 979px) { 适配样式写这里 }
@media (max-width: 767px) { 适配样式写这里 }
@media (max-width: 480px) { 适配样式写这里 }
作为一种妥协的方案,以今天的上帝视角看这套方案确实存在很多问题,比如毫无移动端交互特性,各种设备样式耦合性强等,但是其在那个刀耕火种的年代已然是最佳的方案。其中衍生出的很多概念至今仍然被广泛应用,尤其是这个时代诞生的bootstrap更是影响了后来无数的框架,甚至现在有不少只有APP的网站,仍然选用响应式布局来作为小尺寸设备的适配方案。大名鼎鼎的Ant Design就是其中之一。
2 今生
时钟拨回到现在,随着用户对手机呈现内容的丰富性和多样性要求越来越高,急需一种能高度还原高保真设计稿并且逼近原生app体验的网页适配方式,在这样的背景下,REM应运而生。
传统的开发流程一般可以分为这么几个步骤:
- 需求确定
- 输出交互图
- 输出高保真(750 * 1340)
-
依据高保真输出页面
作为当年野路子的响应式显然是不符合以上流程,而rem则恰恰相反。为了能更好的理解rem适配,下面就几个关键且重要的知识点做个简单讲解:
2.1 知识储备
要说rem就不得不提到一个概念就是:像素。而像素又可以做以下几种区分:
物理像素(physical pixel)
物理像素,顾名思义就是物理设备–显示器上真实存在的像素点,每一个发光的像素点。
设备独立像素(density-independent pixel)
设备独立像素也被称为逻辑像素,这是一种存在于软件层面的虚拟像素。在早期手机下,物理像素和独立像素是一比一的关系,一个物理像素就对应着一个独立像素,自从苹果推出了Retina屏幕以后,相比于之前的设备相同单位面积上物理像素是之前的两倍。比如同样尺寸的iPhone4和iPhone3GS,如果还是一个物理像素对应一个独立像素来渲染,那么在4上能刚好显示完的内容在3GS上就只能显示一半,这就将带出我们下一个概念DPR。
以上图片来自于:PPK大神的博客
设备像素比(device pixel ratio)
上面提到了视网膜屏幕带来的像素增加造成了物理像素和独立像素之间的差异,实际上有这么一个公式来总结这种结果:
设备像素比(DPR) = 物理像素 / 设备独立像素
DPR总结出了物理像素和独立像素之间的对应关系,通过下图能清晰直观的看到这个关系。
以上图片来自于:[screensiz论坛](https://screensiz.es/iphone-8-plus](https://screensiz.es/iphone-8-plus)
详细信息可以查阅https://screensiz.es/iphone-8-plus
CSS像素(device-independent pixel)
CSS像素严格意义上说是被包含于独立像素里,其应用场景主要是在浏览器上,CSS像素和设备像素之间存在这一一个关系:
当设备像素比为1:1时,使用1(1×1)个设备像素显示1个CSS像素;
当设备像素比为2:1时,使用4(2×2)个设备像素显示1个CSS像素;
当设备像素比为3:1时,使用9(3×3)个设备像素显示1个CSS像素。
2.2 适配
了解了移动端适配的原理以后,我们具体该如何来实现呢?其实很简单,大概是以下几步:
设置META
同响应式一样,rem适配同样需要设置meta。应该说所有的移动端适配第一步都是如此,效果也同之前说的一样,主要是告诉手机浏览器(webview)要对viewpoint要开始工作了,并且设置好viewpoint的画布大小和缩放尺寸等信息。
设置 <html>
标签的 font-size
<html>
font-size
这一步是核心,rem之所以能根据设备尺寸和分辨率自适应,根本的原因就是这一步。通过js获取到当前视窗的宽度,然后将当前宽度划分为10等份,取一种一份为
font-size
的值,代码如下:
var iWidth = document.documentElement.clientWidth;
document.getElementsByTagName("html")[0].style.fontSize = iWidth / 10 + "px";
iPhone X的效果如下:
2.3 PX=>REM
众所周知,我们设计稿设计都是以PX为单位,而我们显示需要的是REM。那么万事俱备只欠东风,这个神奇的转换过程又是如何做到的呢?
目前绝大部分移动端的设计稿尺寸都为750px,这里我们就以750为例分解。首先我们取设计稿宽度的1/10为rem
基准点
,然后只需要将设计稿上的
px
除以这个
基准点
即可得到rem,具体公式:
rem=px/基准点
要将px转换成rem方法很多,这里选取一种更常见也是最高效的,通过sass的function和mixin封装到一个公共文件中,并将其全局引入。接下来就以
vue-cli
为例来讲解如何做:
1、安装 node-sass
和 sass-loader
以及 sass-resources-loader
三个包:
node-sass
sass-loader
sass-resources-loader
cnpm i node-sass sass-loade sass-resources-loader -S-dev
2、在webpack的rules的loader中将sass加进去,这里主要是针对组件内部样式:
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.scss$/,
include: '/src/',
loaders: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
}
]
3、全局注册公共样式文件:
既然用到
sass
就必然要将rem转换的方法作为公共方法调用,这里需要将这一系列转换手段写在一个文件中,然后在
main.js
中全局导入,这里就需要用到
sass-resources-loader
对公共文件进行全局注册,这里在
webpack
的
generateLoaders
方法中将
scss
的返回改为这样:
//webpack中全局注册,这里路径为相对路径
scss: generateLoaders('sass').concat({
loader: 'sass-resources-loader',
options: {
resources: path.resolve(__dirname, '../src/style/global.scss')
}
})
//main.js中全局导入
import '@/style/global.scss'
全局样式文件
global.scss
的内容如下:
$baseFontSize: 75;//设计稿基准点
//px转换为rem单个属性,具体调用:width:px2rem(22); ====> width:0.58rem;
@function px2rem($px) {
@return $px*2/$baseFontSize*1rem;
}
//这是处理一个属性的多个值,常见于margin和padding的设置,具体调用为:@include px2rems(margin,22,0,0,22); ===> margin:0.58rem,0,0,0.58rem;
@mixin px2rems($property, $values...) {
$max: length($values);
$remValues: '';
@for $i from 1 through $max {
$value: nth($values, $i);
$remValues: #{$remValues + $value*2/$baseFontSize}rem;
@if $i < $max {
$remValues: #{$remValues + " "};
}
}
#{$property}: $remValues;
}
//高能预警,这里对字体进行了特殊处理
@mixin font-dpr($font-size) {
font-size: $font-size;
[data-dpr="2"] & {
font-size: $font-size * 2;
}
[data-dpr="3"] & {
font-size: $font-size * 3;
}
}
以上方案中有一个值得特别交代的就是,字体一般不推荐用rem。rem是个相对单位,在不同手机会有不同尺寸,如果字体使用rem就会出现一些非主流的尺寸影响美观。但是如果不加以区分直接按照视觉稿的字体大小直接写死也会带来类似的结果,我们需要做的是根据手机的DPR不同设置不同的字号。
font-dpr
这个宏则是专门处理这个的。
细心的朋友大概也发现了,在上面代码中无论是
function
还是
mixin
的传入值我都做了双倍处理,这里是因为我们设计师出图是
375
但是我又想按照
750
来适配做双倍处理即可,狗总不能被尿憋死嘛。至此,移动端适配也该告一段落。