近期,公司准备弄一套移动端开发模板,于是我用vue-cli4建了一个项目,其中模板中涉及到移动端的适配问题,这一块之前一直都很懵懂。一般来说移动端的设计图宽度是750px,因为可以无限下滚,所以高度是动态的,这时候我们就不得不针对这一份设计稿,让其在不同屏幕宽度下显示一致。
具体我们聊聊页面适配需要解决的问题。
一、元素自适应问题
假设我们在设计稿中有个logo宽度是75px。那么这个logo在不同手机屏幕上等比例显示应该是多大呢? 具体的我们来算算:
- 在375px像素的手机上,结果应该是```375/750 * 75 = 37.5px
- 在360px像素的手机上,结果应该是```360/750 * 75 = 36px
- 在320px像素的手机上,结果应该是```320/750 * 75 = 32px
这个算法是什么意思?375/750 ,原本在750px设计稿上的1px 换算到375px 就是0.5px 那实际在屏幕上原本75px的logo就是37.5px。这是我们人工算出然后写死px,可是那么多屏幕,我们不可能每个都去计算的,这肯定是不科学的。于是就有了现在两种比较通用的方案来解决问题。以vw为单位的动态计算元素大小和rem为单位的动态计算
- vw: viewport width,相对于视口的宽度 1vw为视口宽度的1%,100vw为设备的宽度
- rem: 相对于根元素html的字体大小的单位
首先我们先结束一下rem单位的布局,rem布局非常简单,其基本原理就是根据屏幕不同的分辨率,动态修改根字体的大小,让所有的用rem单位的元素跟着屏幕尺寸一起缩放,从而达到自适应的效果。 拿刚刚我们的logo来举例:我们的设计稿的宽度是750px,于是在375px的屏幕上是直接把所有尺寸/2,现在我会这样实现自适应:
html {
font-size: calc(100vw / 750);
}
.logo{
width: 75rem // 这个就等于75 * 100vw/750 = 75 * 375/750 = 37.5px
}
其中,100vw是设备宽度deviceWidth,也就是上面我们所说的375px像素的手机,或者是360px像素的手机,这样就实现了不同设备宽度下,动态修改根字体font-size的大小,从而动态改变各元素的大小。
那么是不是只要按这种情况,我们就可以直接按设计稿的元素大小写呢?
其实是:不可以,因为在移动端上最小字体限制的,最小的字体是8px;
所以100vw / 750是不可能实现的, 因为最终会得到0.5px的字体,这是和移动端最小字体相违背的,那么上面的结果是
.logo {
width: 75rem // 75*8 = 600px
}
所以我们这边采用
html {
//这里是按设计稿750px来换算,这样子750px宽度下根元素字体大小则为750px/7.5=100px=1rem,这样子涉及的原因其实就是为了换算方便
font-size: calc(100vw / 7.5);
// 同时,通过媒体限制根元素字体最大最小值,避免元素动态计算过大或者过小
@media screen and (min-width: 320px) {
font-size: 64px;
}
@media screen and (max-width: 540px) {
font-size: 108px;
}
}
.logo {
width: .75rem // 这就是75px
}
当然加上优化的话,我们加上最小最大宽度限制,这样子便不会在大屏出现问题
// body 也增加最大最小宽度限制,避免默认100%宽度的 block 元素跟随 body 而过大过小
body {
max-width: 540px;
min-width: 320px;
}
同理用vw为单位的话 1vw = 1px
deviceWidth = 320,logo = 75 / 320 * 100 = 23.4375vw
deviceWidth = 375,font-size = 75 / 375 * 100 = 20vw
deviceWidth = 414,font-size = 75 / 414 * 100 = 18.115942vw
这里公式的意思就是 1vw 就是页面的 1% 于是 75 / 320 就是占了页面的多少, 乘于100是换算成百分比。
当然你可以直接用scss写成函数
$vw_base: 750;
@function vw($px) {
@return ($px / 750) * 100vw;
}
.logo {
width: vw(75px);
}
二、关于大家谈到的1px问题
什么是 1px问题 ?
这个要从乔布斯在iPhone4的发布会上首次提出了Retina Display(视网膜屏幕)的概念,在iPhone4使用的视网膜屏幕中,把2x2个像素当1个像素使用,这样让屏幕看起来更精致,但是元素的大小却不会改变。从此以后高分辨率的设备,多了一个逻辑像素。这些设备逻辑像素的差别虽然不会跨度很大,但是仍然有点差别,于是便诞生了移动端页面需要适配这个问题,既然逻辑像素由物理像素得来,那他们就会有一个像素比值,设备像素比(dpr) = 物理像素/设备独立像素。如 iphone 6、7、8 的 dpr 为 2,那么一个设备独立像素便为 4 个物理像素,因此在 css 上设置的 1px 在其屏幕上占据的是 2个物理像素,0.5px 对应的才是其所能展示的最小单位。这就是 1px 问题。

1px问题解决方案
方案1:使用css3的 scaleY(0.5) 来解决
/* 底边框 */
.b-border {
position: relative;
}
.b-border:before {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
background: #d9d9d9;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
/* 上边框 */
.t-border {
position: relative;
}
.t-border:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background: #d9d9d9;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
/* 右边框 */
.r-border {
position: relative;
}
.r-border:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
width: 1px;
height: 100%;
background: #d9d9d9;
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
/* 左边框 */
.l-border {
position: relative;
}
.l-border:before {
content: '';
position: absolute;
left: 0;
bottom: 0;
width: 1px;
height: 100%;
background: #d9d9d9;
-webkit-transform: scaleX(0.5);
transform: scaleX(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
/* 四条边 */
.setBorderAll {
position: relative;
&:after {
content: ' ';
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
transform: scale(0.5);
transform-origin: left top;
box-sizing: border-box;
border: 1px solid #e5e5e5;
border-radius: 4px;
}
}
原理:将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍。 此时我们可以把这解决方案抽出来当作一个公共样式进行调用,并加上媒体查询做兼容处理。
/* 四条边 */
.setBorderAll(@border_clolor,@border_radius: 5px) {
position: relative;
&:after {
content: ' ';
position: absolute;
top: 0;
left: 0;
transform: scale(0.5);
transform-origin: left top;
box-sizing: border-box;
border: 1px solid @border_clolor;
@media (-webkit-min-device-pixel-ratio: 1.5){
width: 150%;
height: 150%;
transform: scale(0.66666);
border-radius: cacl(#{@border_radius} * 1.5);
}
@media (-webkit-min-device-pixel-ratio: 2){
width: 200%;
height: 200%;
transform: scale(0.5);
border-radius: cacl(#{@border_radius} * 2);
}
@media (-webkit-min-device-pixel-ratio: 3){
width: 300%;
height: 300%;
transform: scale(0.33333);
border-radius: cacl(#{@border_radius} * 3);
}
}
}
方案2:页面缩放解决问题
这个方案的思路就是上面边框的延续将整个页面缩小dpr倍,再将页面的根字体放大dpr倍。这样页面虽然变小了,但是由于页面整体采用rem单位,当根字体放大dpr倍以后,整体都放大了,看上去整体样式没什么变化。

假如以下手机的 dpr=2对于dpr=2的手机设备,1px就会有 2x2 的物理像素来渲染,但是当缩放以后其实就变成 1x1 个单位渲染了,看下面示意图:

//获取屏幕宽度、dpr值
var deviceWidth = document.documentElement.clientWidth,
dpr = window.devicePixelRatio || 1;
//设置根字体扩大dpr倍
//由于deviceWidth当页面缩小dpr倍时,本身获取的值就增加dpr倍
//所以这里不需要再乘以dpr了
document.documentElement.style.fontSize = deviceWidth + 'px';
//设置页面缩放dpr倍
document.getElementsByName('viewport')[0]
.setAttribute('content','width=device-width;initial-scale=' + 1/dpr)
最后给大家推荐几篇文章,其实我也是看了以下几篇文章做到总结,其实适配问题远远不止如此,我只是针对自己的模板然后参考了以下几篇文章做的一部分优化。 参考与推荐文章: