阅读 694

聊聊移动端的适配问题

近期,公司准备弄一套移动端开发模板,于是我用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)
复制代码

最后给大家推荐几篇文章,其实我也是看了以下几篇文章做到总结,其实适配问题远远不止如此,我只是针对自己的模板然后参考了以下几篇文章做的一部分优化。 参考与推荐文章: