移动端适配-实践篇

这是一个系列文章,分 3 篇:
javascript

通常咱们在作前端项目以前,都会先拿到视觉稿和交互稿,咱们能够根据视觉稿上的尺寸、颜色等信息编写 CSS 样式,能够根据交互稿来写 JS。css

每一个公司的视觉规范不一样,视觉稿也就不一样。甚至在一些大公司里,每一个部门都有本身的视觉规范。好比页面画布大小是以 640 为基准仍是 750。最终给前端开发人员的文件也可能不一样,好比 PSD 文件、Sketch 文件或者图片文件,其中包括页面文件和切图。html

我作过的项目里,有这几种状况:前端

  • Sketch文件包 + 整体设计图 + 每一个页面图片 + 切图(@2x、@3x 各一份)
  • PSD 源文件 + 整体设计图 + 每一个页面图片(有的带尺寸/颜色标注) + 切图(@2x、@3x 各一份)
  • 只给带标注的图片的,这种我都会问UED要源文件,标注很难画全的。

咱们 UED 画布基准都是 iphone6,可是有的是给的画布宽 375 的 Sketch 文件,有的给画布宽 750 的 PSD 文件。java

下面以我作过的一个页面为例,左边是Sketch 文件的截图,右边是 PSD 文件导出的图片。python

这个页面涉及到了移动端适配的几个问题:android

  • 布局适配,不一样屏幕尺寸的设备中布局一致
  • 图片高清适配,不一样分辨率设备中图片高清展现
  • 不一样分辨率设备中 1px 边框显示一致
  • 内容适配,不一样屏幕尺寸的设备中的文字大小

那,拿到视觉稿了,咱们要开始写代码了。ios

假如咱们拿到的是宽 750 的视觉稿
**
在写 CSS 代码前还得作件事儿,手机端布局视口默认状况下是 768px ~ 1024px 之间,手机屏幕宽度你们知道的iphone5 320px, iphone6 375px, iphone6 plus 414px 这样子。web

像下面图示这样,在手机上浏览页面时得横向滚动,是否是很不友好?chrome



因此咱们得想办法,让页面横向内容都展现在屏幕可视范围中,而且禁止缩放。有两种办法:
  • 设置布局视口宽度为一个定值,而后按照布局视口的宽度与屏幕宽度的比例缩放
  • 设置布局视口的宽度 = 设备宽度

设置布局视口宽度为一个定值,按照布局视口的宽度与屏幕宽度的比例缩放

好比屏幕宽 375px,视觉稿 750px,那就设置布局视口的宽度 width=750,
scale = 375 / 750 = 0.5

<meta name="viewport" content="width=750,initial-scale=0.5,maximum-scale=0.5,user-scalable=no">
复制代码

再好比屏幕宽 320px,视觉稿 750px,那就设置布局视口的宽度 width=750,
scale = 320 / 750

固然这是须要根据屏幕宽度动态设置的:

(function(){
	var doc = window.document;
	var metaEl = doc.querySelector('meta[name="viewport"]');
	if(!metaEl){
		metaEl = doc.createElement("meta");
		metaEl.setAttribute("name", "viewport");
	}
	var metaCtt = metaEl ? metaEl.content : '';
	var matchWidth = metaCtt.match(/width=([^,\s]+)/);
	var width = matchWidth ? matchWidth[1] : 750
	if(width == 'device-width'){return}
	
	var screenWidth = window.screen.width;
	var scale = screenWidth/width;
	metaEl.setAttribute("content", "width="+ width +",user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
})()
复制代码

demo1
下面是依次在 iphone6 plus,iphone6,iphone5 上的展现效果。

image.png

那有人就会问啦,给的视觉稿是基于 iphone5 的,画布宽度 640 怎么办?
**
那 CSS 编程就按照视觉给的来,width 设置 640,在宽 640 的布局视口,CSS 样式编写与视觉稿相同,那布局就能与视觉稿相同啦。再经过动态缩放就能把整个页面完整的展现在可视窗口中啦。

那又有人问啦,给的不是 750 宽的视觉稿,是 Sketch 视觉稿,宽 375,这怎么办?
**
同样的道理,视觉稿宽 375,视觉稿中的元素都是按宽 375 来布局的,那咱们布局视口 width 设置 375,CSS 样式按375的视觉稿来写就好啦,最后就是动态缩放啦。

好啦,那咱们来看这种方式能不能解决咱们遇到的几个问题。

图片高清问题

**
实际要解决的是怎么让 1 个位图像素正好覆盖 1 个物理像素,这样可让图片在不一样 dpr 的手机上效果同样。

  • dpr = 1,须要 1 倍图
  • dpr = 2,须要 2 倍图
  • dpr = 3,须要 3 倍图

最好的解决办法是:不一样的 dpr 下,加载不一样的尺寸的图片

1px 边框问题

**
实际要解决的是怎么让 1 个 CSS 像素正好覆盖 1 个物理像素,这样可让 1px 边框在不一样 dpr 的手机上同样细。
可是布局视口缩放比为 1/dpr 才能让 1 个 CSS 像素是否正好覆盖1个物理像素。

显然这种按照布局视口的宽度与屏幕宽度的比例缩放的方式,只有与视觉稿画布基准一致的设备才能与视觉稿要求显示的一致。好比这里只有在 iphone6 上 1 个 CSS 像素是否正好覆盖1个物理像素(横向 200px 的元素对应 400 个物理像素,在缩放 0.5 后,横向 200px 的元素对应 200 个物理像素,1:1)

布局适配的问题

其实直接从上面的对比图就能够看出来,在不一样设备上布局是一致的。
原理很简单: 咱们写 CSS 原本就是按视觉稿来写的,而后总体缩放,以适应各类宽度的设备,元素的比例没有变。

内容适配的问题

主要是文字大小,由于是总体缩放,屏幕越大,字体也越大。
这个主要看视觉规范怎么定,有的以为这样挺好,有的就但愿字体大小都同样,还有的但愿字体大小不是根据屏幕大小按比例缩放而是对必定范围内的屏幕宽度设定特定的字体大小。

总结

【原理】

设定布局视口宽度为视觉稿画布宽度,动态设置缩放比例scale=屏幕宽度/视觉稿画布宽度

【优势】

实现简单,可解决不一样屏幕大小的布局问题,在各类屏幕上布局一致

【缺点】

  • 不能解决 1px 边框问题;
  • 缩放值依赖于屏幕宽度,demo 里是经过 screen.width 获取屏幕宽度的,这在 chrome 里 iphone6 返回的是屏幕宽度值 375,但在其余浏览器就不必定了,好比safari中返回的是 1280。
  • 全部元素都会缩放,好比字体,在 iphone5 上就会小不少,这不必定是你们想要的。

设置布局视口的宽度 = 设备宽度

<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
复制代码

这种状况下有什么效果呢?

demo2

图片太长,横着放了^^

image.png

看看看,都被挤掉了,为啥?
这个是 iphone6 的截图,如今设置了布局视口宽度是屏幕宽度 375,咱们视觉稿是按 iphone6 二倍图来的宽 750,那固然就放不下了,好比视觉稿中图片占位 400px x 400px,咱们 CSS 中写的:

img{
	width: 400px;
	height: 400px;
}
复制代码

可是布局视口宽度就 375,图片就放不下了。
那 iphone6 布局视口宽只有 375,咱们把视觉稿尺寸所有按比例缩小到 375 就好啦。那 750 到 375,须要除以 2,咱们在编写 CSS 样式时除以 2 就好啦。

上面视觉稿中图片是 400 x 400,咱们 CSS 中就写:

img{
	width: 200px;
	height: 200px;
}
复制代码

其余元素的宽高、边框、内边距、外边距、字体大小等,统统先除以 2,统统先除以 2。那如今的效果是这样的,依次是 iphone6 plus,iphone6, iphone5

image.png

如今,咱们能看到 100% 呈如今可视窗口中了。那再来看看咱们遇到的问题解决了吗?

**1px 边框问题

设置 0.5px,在 dpr=2 的设备中对应 1 个物理像素,在 dpr=3 的设备中对应 1.5 个物理像素。
并且在 dpr=2 的设备中设置 0.5px 不必定能达到咱们想要的效果,看下面:

image.png

明明设置的 0.5px,但浏览器实际仍是按 1px 处理的。ios7 如下,android 等其余系统里,0.5px 会被当成为 0px 处理? 因此直接设置成 0.5px 是不可行的。 这实际上仍是须要 1 个 CSS 像素正好覆盖 1  个物理像素就能够解决的。

**布局适配的问题

由于没有按设备属性动态缩放,每一个设备上每一个css像素大小都同样,200px 大小的元素在每一个设备上也够同样长。
其实从上面的对比图就能看出来,图片容器(灰色背景)在不一样屏幕大小的手机都是同样的高,整个页面在小屏手机中就须要向下滚动查看,一排展现的 4 个图片标签在大屏手机中右边的空白就比较大。布局问题也没有解决。

**内容适配的问题 **
文字大小通通同样,也没有解决。

1 个 css 像素只占 1 个物理像素

前面已经讲了几遍了,要解决 1px 边框的问题,达到在不一样分辨率设备上效果一致(固然也与视觉稿一致),须要让不一样分辨率设备上  1 个 css 像素只覆盖 1 个物理像素

前面咱们将 750 视觉稿中的尺寸除以 2 来编写样式,只能在 iphone6 上的效果知足。那如今咱们在布局宽度=屏幕宽度时,让缩放比例为 1/dpr,就可让 1 个 css像素只覆盖1个物理像素

image.png

好比 750 的视觉稿,元素边框 1px,咱们 CSS 样式也写:

div{
	border-width: 1px;
}
复制代码

而后 JS 动态设置缩放逻辑大概是这样子:

var doc = document;
var docEl = document.documentElement;
var isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
var dpr = window.devicePixelRatio || 1;
if (!isIos){ dpr = 1 }
var scale = 1 / dpr;
var metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
    metaEl = doc.createElement("meta");
    metaEl.setAttribute("name", "viewport");
    doc.head.appendChild(metaEl)
}
metaEl.setAttribute("content", "width=device-width,user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
复制代码

那如今不管什么分辨率的设备,1个css像素实际只占1个物理像素了。
那如今效果是什么样子呢?

demo3

image.png

全部元素尺寸都缩小 1/dpr 了,本质上是 1 个 CSS 像素宽度缩小了 1/dpr,不一样 dpr 的设备上 CSS 像素大小就不一样了。那咱们 CSS 样式 200px,在不一样 dpr 的设备上所占宽度也不一样了。

注意: 若是在安卓设备上查看 demo3,与 demo2 的效果相同,由于 dpr 都被处理成 dpr=1,没有缩放,CSS 样式又与 750 视觉稿一致。因此这种方式只缩放,不控制布局是不行的。
**
这个问题能够在待会讲布局适配时解决,布局适配的目的是让元素在不一样设备上占比一致(与视觉稿相同。)

解决布局适配问题

能够看下demo3的图

  • 不一样屏幕大小的手机都是同样的高,在小屏手机中就须要向下滚动查看。
  • 选择图片标签下一行展现4个,但在大屏手机中右边的空白就比较大。

那咱们但愿,元素在布局视口中的占比(包括元素宽高、边距等)在不一样设备上都相同
同一份样式怎么让元素占比相同呢?用绝对单位 px 确定是不行的,咱们考虑相对单位 rem。

假若有个宽能够表示为 20rem 的元素:

CSS 样式 CSS 像素个数
width:20rem 20 * html元素的font-size
元素占比 = 元素 CSS 像素个数 / 布局视口宽度
        = 20 * html元素的font-size / 布局视口宽度
复制代码

因此只要知足:

常量值 *  HTML 元素的 font-size =  布局视口宽度
复制代码

元素占比在任何设备上都是某一个定值了。
那咱们只要按下面的公式动态设置 HTML 元素的 font-size 就好啦。

HTML 元素的 font-size = 布局视口宽度 / 常量值
复制代码

这个常量值能够是任意一个常数,但为了写样式方便,咱们会作一些考虑,例如:

为了解决前面 1px 边框 retina 屏显示的问题,页面缩放了 1/dpr,iphone6 布局视口的宽度就是:375 * 2 个 css 像素
咱们可让 iphone6 中 HTML 元素的 font-size = 375 * 2 / 7.5 = 100,这样能够直接将视觉稿中的尺寸除以100,获得 rem 单位的数值

对于以 iphone6 为基准的 750 视觉稿,而且缩放 1/dpr,HTML 元素的 font-size 与 布局视口宽度视口的比例能够是:

HTML 元素的 font-size = 布局视口宽度 / 7.5
复制代码

对于以 iphone5 为基准的 640 视觉稿,而且缩放 1/dpr

HTML 元素的 font-size = 布局视口宽度 / 6.4
复制代码

大概的实现就是这样子:

var doc = document;
var docEl = document.documentElement;
var isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);
var dpr = window.devicePixelRatio || 1;
if (!isIos){ dpr = 1 }
var scale = 1 / dpr;
var metaEl = doc.querySelector('meta[name="viewport"]');
if (!metaEl) {
    metaEl = doc.createElement("meta");
    metaEl.setAttribute("name", "viewport");
    doc.head.appendChild(metaEl)
}
metaEl.setAttribute("content", "width=device-width,user-scalable=no,initial-scale=" + scale + ",maximum-scale=" + scale + ",minimum-scale=" + scale);
setTimeout(function(){
	var width = docEl.getBoundingClientRect().width;
	docEl.style.fontSize = width / 7.5 + 'px';
})
复制代码

由于 scale=1/dpr && 基于 750 宽的视觉稿,元素的CSS 样式(rem)能够直接由视觉稿尺寸除以 100
最后的效果是:

demo4

HTML 元素的 font-size = 布局视口宽度 / 10
复制代码

demo5

HTML 元素的 font-size = 布局视口宽度 / 7.5
复制代码

image.png

那有人会问啦,个人视觉稿是基于 iphone6 的,但给的是一倍图宽 375 怎么办?
**
视觉稿通常有两种类型: 一倍图 和 二倍图,好比基于 iphone6 的:

画布宽 750,元素 400 x 400
画布宽 375,元素 200 x 200

缩放通常有两种:

scale = 1,布局视口宽度 = 375 个 CSS 像素
scale = 1/dpr,布局视口宽度 = 750 个 CSS 像素

若是视觉稿选择画布宽 750,而且 scale=1,那元素 CSS 像素应该由视觉稿中的尺寸除以 2,使布局视口宽度 375 中 400/2 x 400/2 与 视觉稿宽 750 中 400 x 400 的元素比例一致

若是视觉稿选择画布宽 375,而且 scale=1/dpr,那元素 CSS 像素应该由视觉稿中的尺寸乘以 2,使布局视口宽度 750 中的元素 200x2 x 200x2 与 视觉稿宽 375 中 400 x 400 的元素比例一致
其余状况,元素 CSS 像素值就是视觉稿中的值。

知道这个以后,再决定 px 与 rem 的换算值:HTML 元素的 font-size,而后再写 CSS 样式(rem)。
举几个例子:

若是视觉稿选择画布宽 750,而且 scale=1,HTML 元素的 font-size = 375 / 7.5 = 50,400/2/50= 4rem

img{
    width: 4rem;
    height: 4rem;
}
复制代码

若是视觉稿选择画布宽 375,而且 scale=1/dpr,HTML 元素的 font-size = 750 / 7.5 = 100,200 * 2 /100 = 4rem

img{
	  width: 4rem;
	  height: 4rem;
  }
复制代码

画布宽 750 & scale=1/dpr,HTML 元素的 font-size = 750 / 7.5 = 100,400 / 100 = 4rem

img{
	  width: 4rem;
	  height: 4rem;
  }
复制代码

画布宽 375 & scale=1,HTML 元素的 font-size = 375 / 7.5 = 50,200 / 50 = 4rem

img{
	  width: 4rem;
	  height: 4rem;
  }
复制代码

总结

一、设置 HTML 元素的 font-size 与布局视口宽度成比例,CSS 样式使用相对单位 rem,元素尺寸也就与布局视口宽度成比例,从而在每一个设备布局一致。用视觉稿来肯定这个比例值就能与视觉稿的布局一致。

二、 设置缩放比例为 1/dpr 能够解决 1px 边框问题。
 
为了 rem 与 px 换算方便,选定的比例值可让 HTML 元素的 font-size 为 100px。
iphone6 就是 7.5,iphone5 就是 6.4,不一样基准的视觉稿 rem 与 px 换算比例都是 100.
最后,若是不想动态适配的内容,能够直接使用 px 单位,好比字体,还有1px边框。

淘宝和网易的作法

这里讲的适配方案是今天 [2017-08-11] 的,之后网站方案可能会有改动,因此我把代码保存下来了,能够看适配代码
**

手机淘宝

淘宝统必定为 10,iphone6 是 1rem = 75px,iphone5 1rem = 64px,能够到手机淘宝验证。不一样基准的视觉稿 rem 与 px 换算比例不一样。

网易

并无作缩放,只使用了相对单位 rem。

HTML 的 font-size = 布局视口宽度/7.5 = 设备屏幕宽度 / 7.5
复制代码

在 iphone6 上,HTML 的 font-size 为 375/7.5 = 50px,若是他们的视觉稿是 1 倍图,那就是直接除以 50 来写 CSS 的,若是是二倍图,就是除以 100 来写 CSS 的。

这个比例能够随便设置,根据实际的需求,网易设置 7.5 时考虑换算简单,淘宝设置 10 则是考虑兼容 vw。

总结

动态修改 HTML 元素的 font-size 和动态修改 viewport 在不少安卓设备上有兼容性问题。如今愈来愈多的浏览器支持 vw、vh,viewport+rem 的方案不必定是最好的方案。

对于 1px 边框的问题,也有不少其余的解决方案。

  • 判断终端类型是否支持 0.5px,支持则直接定义边框为 0.5px
  • 使用 CSS 定义样式:-webkit-transform: scale(0.5),对边框进行缩放
  • 使用 CSS 实现阴影代替边框:-webkit-box-shadow:0 1px 1px -1px rgba(0, 0, 0, 0.5)
  • 使用 CSS 定义背景样式(background-image)来实现边框
相关文章
相关标签/搜索