随着技术革新,出现形形色色的智能移动设备,所谓“移动端适配”即是在任意移动设备上保证开发的页面显示正确。在研究如何具体适配之前可以先了解关于像素的一些概念。
不做适配
如下:
<div class="tabbar">
<div>微信</div>
<div>通讯录</div>
<div>发现</div>
<div>我</div>
</div>
.tabbar {
width: 375px;
div {
width: 25%;
}
}
同一个页面在iPhone6/7/8中查看一切正常,因为iPhone的设备宽度是375px,但是在iPhone6/7/8 Plus下设备宽度是414px,导致375px无法占满屏幕宽,这时候就需要进行适配处理。
百分比适配
百分比适配是一种根据设计图的尺寸和设备的分辨率以百分比的方式进行换算和适配的方法。通过计算设计图上的像素与目标设备分辨率的比例可以得到百分比像素的值,从而实现在不同分辨率设备上保持一致的布局和显示效果。
比如app底部的tabbar可以通过百分比实现:
<div class="tabbar">
<div>微信</div>
<div>通讯录</div>
<div>发现</div>
<div>我</div>
</div>
.tabbar {
width: 100%;
div {
width: 25%;
}
}
以上代码中.tabbar下的四个<div>宽度都为25%,相对的是父级宽度的百分比,因此在任何设备下显示都是占相同比例。
但
要注意百分比布局不全都是根据父级宽度进行计算,如:
- [max/min-]height、top、bottom等:取决于父级高度的百分比;
- padding、margin:取决于父级宽度百分比;
- transform: translate()、background-size等:取决于自身宽度百分比;
viewport适配
如果我们开发时是按照375px进行编码,在不做其他自适应的情况下,可以通过缩放viewport的方式让网页显示正常。
计算缩放的比例:设备宽度 / 375,假设是iPhone6/7/8 Plus,就是414 / 475 = 1.104,其他设备宽度可以使用document.documentElement.clientWidth来获取,得到相应缩放比。
在<meta name="viewport"></meta>中设置缩放比例,js代码如下:
(function () {
const view = document.querySelector('meta[name="viewport"]');
const targetWidth = 375;
// 获取设备宽度
const curWidth = document.documentElement.clientWidth;
const targetScale = curWidth / targetWidth;
view.content = `initial-scale=${targetScale},user-scalable=no,minimum-scale=${targetScale},maximum-scale=${targetScale}`;
})();
以上代码在iPhone6/7/8 Plus设备下最终会得到:
<meta name="viewport" content="initial-scale=1.104,user-scalable=no,minimum-scale=1.104,maximum-scale=1.104">
但
viewport并非完美无缺,主要有以下几个缺点:
- 影响搜索引擎优化(SEO):一些搜索引擎不会跟随viewport中的URL,可能会导致搜索引擎记录错误的URL;
- 一些小设备可能会出现文字过小、内容显示不全等问题;
- 算出的值在有小数点的情况下可能出现部分误差,因为设备独立像素不能有小数;
DPR缩放适配
在# 可以掌握的知识之移动端像素概念 一文中详解了DPR的概念。
在移动端开发中,UI给我们的设计图稿一般是750px起稿
- 当
DPR = 1时,一个物理像素等于一个css像素 - 当
DPR = 2时,四个物理像素显示一个css像素
每一个位图像素都包含着显示信息(如:显示位置、颜色值、透明度等)。
理论上,一个位图像素对应一个物理像素,图片才能得到完美清晰的展示,对于retina视网膜屏幕而言,会出现位图像素点不够的情况,一个位图像素对应四个物理像素,由于单个位图像素不能再进一步分割,只能就近取色,从而导致图片模糊,如下图所示:
所以在高清屏幕上使用二倍图是一个更好的方案,如200 * 100的<img>标签,需要提供400 * 200的图片,如此,位图像素点个数就是原先的4倍,在retina视网膜屏幕下,位图像素点个数与物理像素点个数形成1 : 1,图片自然也清晰了,这也解释了为什么视觉稿的画布大小要 x 2。
那么
在普通屏幕下使用了二倍图会怎么样呢?
在200 * 100的img标签,对应的像素点个数是200 * 100个,还是这张二倍图的像素个数为200 * 100 * 4,会出现一个物理像素点对应四个位图像素点的情况,所以它的取色只能通过一些特定算法(显示结果就是一张只有原图像素总数四分之一,称这个过程为downsampling(缩减像素采样)),肉眼看上去图片不会模糊,但是图片会缺少一些锐度,或者部分色差(可接受范围)。
图片高清显示
现在我们手里有一张400 * 600的@2x图片,把它放在缩小50%即200 * 300的容器中:
img {
width: 200px;
height: 300px;
}
背景图:
width: 200px;
height: 300px;
background-image: url(img@2x.png);
background-size: 200px 300px;
缺点很明显:
- 在普通屏幕下,同样下载@2x的图片会造成资源浪费;
- 图片由于
downsampling,会失去一些锐度、色差
解决办法
在不同的DPR下加载不同尺寸的图片,可以通过css媒体查询或js判断。
但是这样需要提供两套图片(@1x、@2x)。
retina下,很多设计稿上的参数不能像web一样直接拿下来用,都需要进行适配。例如:同样是1px,移动端的1px就会显得很粗。
在早先的移动设备中,屏幕像素密度都比较低,如iPhone 3,它的分辨率为
320x480,在iPhone 3上,一个css像素确实是等于一个屏幕物理像素的。后来随着技术的发展,移动设备的屏幕像素密度越来越高,从iPhone 4开始,苹果公司便推出了所谓的Retina屏,分辨率提高了一倍,变成640x960,但屏幕尺寸却没变化,这就意味着同样大小的屏幕上,像素却多了一倍,这时一个css像素是等于两个物理像素的。
那么,在设计稿中设计师给出的边框是1px,其实就是在retina屏幕下1物理像素宽,对于css而言,就是0.5px,那把边框设为0.5px不就完事了么?但是:
WWDC大会上给出了1px方案,当写成0.5px时,会显示一个物理像素宽。retina屏的浏览器有可能不认识0.5px的边框,会把它解释成0px即没有边框,包括 iOS 7 和 之前版本,OS X Mavericks 及以前版本,还有 Android 设备。
那么基于DPR的缩放适配方式,其原理就是将css像素缩放成与设备一样大的尺寸!
在我们的实际开发中,设计者为了页面的高清,都是采用物理像素值进行涉及,如iPhone 6/7/8设备宽度为375px,则将其缩放为750px。
使用DPR就散缩放比就是:375 / 750 = 1 / 2
(function () {
var view = document.querySelector('meta[name="viewport"]');
// 获取设备的DPR
var targetScale = 1 / window.devicePixelRatio;
view.content = `initial-scale=${targetScale},user-scalable=no,minimum-scale=${targetScale},maximum-scale=${targetScale}`;
})();
运行:
按照DPR缩放适配后反而占不满了,因为给的宽度是375px,因此只占了一半,那么这种适配方案的意义是什么呢?
实际上这种方案最大的意义就是开发者和设计者的像素都是统一的,因为 UI 是按照 750px 来设计的(iPhone6/7/8为例),那么前端在量图的时候,也是以 750px 为基准。通过 DPR 适配,量出来多少写代码时就可以设置多少,不需要换算更加便捷。
但这种方案并没有真正解决适配问题,因为设计稿是 750px 像素,前端测量出来的也是 750px,但是如果将宽度设置为 750px,在 iPhone 6/7/8 Plus又存在适配问题了,那么就需要配合 rem 进行适配了。
rem适配
rem(font size of the root element)适配是移动端比较主流的一种适配方案!rem的值是根据元素html字体大小来计算的,即1rem = html font-size。
- 如果
html元素没有指定字体大小,浏览器的默认字体大小为16px,所以1rem = 16px; - 如果
html元素指定font-size: 1px,那么1rem = 1px; - 如果
html元素指定font-size: 100px,那么1rem = 100px;
rem适配原理
rem适配原理就是把设备宽度分成相同的若干份,再计算元素宽度所占的份数。
假设现在有两台设备iPhone5 和 iPhone6,它们对应的设备宽度分别为 320px 和 375px,现在将其分为100列,那么每一列为 3.2px 和 3.75px。不同的设备宽度对应的每一列的宽度都不一样。在设置元素的宽度时,以列为参照即可。
同样一个<div>,设置它的宽度为10份,在iPhone 5中该<div>的宽度是32px,而iPhone 6中该<div>的宽度是37.5px。通过这种方式就能实现在不同设备中同一个元素等比例缩放的需求。
px2rem、lib-flexible
现在有很多很优秀的插件:
px2rem,可以把我们写的px直接转换成rem,我们直接对照ui设计稿开发就行;- 淘宝项目组开发的
lib-flexible,会根据屏幕大小为页面自动添加标签,动态控制initial-scale,maximum-scale,minimum-scale等属性的值。让我们可以根据设计图的px尺寸计算相应的rem值,从而使得页面根据屏幕大小自动适配。使用这些插件可以大大节省我们转换单位的时间!
延伸
em是以父元素的字体大小来计算,rem顾名思义是root em,是根据根(html)字体大小计算的。
vw、vh适配
vh 和 vw 都是相对于视窗的宽高度, “视窗”所指为浏览器内部的可视区域大小,即window.innerWidth/window.innerHeight大小,不包含任务栏标题栏以及底部工具栏的浏览器区域大小。
-
vw的全称是 Viewport Width,1vw 相当于window.innerWidth的 1% -
vh的全称是 Viewport Height,1vh 相当于window.innerHeight的 1% -
vmin的值是当前vw和vh中较小的值 -
vmax的值是当前vw和vh中较大的值
另多一嘴之:vh的bug
在移动端的Chrome和Safari上会溢出:
由于浏览器在使用过程中,地址栏时隐时现,从而会改变可视窗口的大小。当地址栏处于视图中时,元素底部被裁剪(右),但我们想要的是元素能完整的占据一屏(左)。
造成这种现象的原因就在于移动端浏览器对于 vh 单位的计算,是不包含地址栏的,也就是说 100vh 的高度会使带有地址栏的视图溢出。
参考文献:
💖致谢!