如何在Vue项目中使用rem实现移动端适配

面向场景:移动端 Web 页面(微信),需要全局适配各种不同分辨率的的手机。

基本概念

  • 物理像素(physical pixel):

一个物理像素是显示器(手机屏幕)上最小的物理显示单元,pp 只与硬件设备有关。屏幕可视宽为 320 像素,非 retina 屏的 pp 是 320,retina 屏的则至少为 640

  • 设备像素比(density-independent pixel)
1
设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向
  • 设备独立像素(device pixel ratio ):

dpr是个虚拟单位,在 web 世界中也称为 css 像素[逻辑像素],屏幕可视宽为 320 像素,dip 也就是 320 像素

部分机型示例

机型 iPhone 5/SE iPhone 6/7/8 iPhone 6/7/8 Plus
独立像素(CSS像素) 320*568 375*678 414*736
物理像素(分辨率) 640*1136 750*1334 1920*1080(1242x2208)
设备像素比(dpr) 2 2 3

相关JS Web API

1
2
3
4
window.devicePixelRatio  // 设备像素比dpr
window.screen.width // 设备屏幕宽度
window.document.documentElement.clientWidth // 获取屏幕的可视区域的宽度(一般用这个)
window.document.documentElement.getBoundingClientRect().width // html元素相对于页面左上角的距离

适配方案

基于淘宝的 flexible.js(目前使用)

在入口页面的 head 标签内中添加以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<script>
!(function flexible(window) {
/* 设计图文档宽度 */
var docWidth = 750;

var doc = window.document,
docEl = doc.documentElement,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize';
var recalc = function () {
var clientWidth = docEl.clientWidth;
docEl.style.fontSize = 100 * (clientWidth / docWidth) + 'px';

};
recalc();
if (!doc.addEventListener) return;
window.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);
})(window);
</script>

这里我们使用的设计稿尺寸是以750px为基础设计的,所以要把视觉稿中的px转换成rem

  • rem计算方式:设计图尺寸px / 100 = 实际rem 【例: 100px = 1rem,50px = 0.50rem】;

使用postcss-pxtorem,自动将px转为rem,灰常便利

1
2
3
4
5
6
7
8
9
// px单位大写将忽略转化rem,例如border-width: 2PX;  // ignored
// 注意:以:style=""这样的方式设置的px像素值,其值为实际像素,不会进行转化
pxtorem({
rootValue: 50, //根元素字体大小
unitPrecision: 3, // 指定`px`转换为`rem`单位值的小数位数(很多时候无法整除)
propList: ['*', '!font*'], // 指定可以从`px`更改为`rem`的属性
selectorBlackList: ['.ignore ','.hairlines',], // 指定不转换为`rem`单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
minPixelValue: 2, // 设置要替换的最小像素值(大于或等于`2px`才转换为`rem`单位),你也可以设置为你想要的值
})

Tips

  • 淘宝那边的适配方案,不推荐用rem作为文本字体大小单位。所以对于字体的设置,仍旧使用px作为单位,并配合用data-dpr属性来区分不同dpr下的的大小

    来自淘宝 lib-flexible:现在绝大多数的字体文件都自带一些点阵尺寸,通常是16px和24px,所以我们不希望出现13px和15px这样的奇葩尺寸。

来自淘宝 lib-flexible:“针对OS 9_3的UA,做临时处理,强制dpr为1,即scale也为1,虽然牺牲了这些版本上的高清方案,但是也只能这么处理了”;

所以我们干脆直接对所有字体大小使用 px 作为单位来定义,放弃data-dpr标识的区分,很鸡肋。(个人推荐用的px做字体单位)

  • 一些不推荐使用rem的CSS属性:border-width、border-radius、box-shadow、transform、background-size
  • 注意只有当你需要某元素的单位要根据屏幕宽度大小变化时,才需要rem、em这类动态宽度单位。一般情况下字体大小是没必要根据屏幕宽度变化

    使用vw,viewport 单位,表示将屏幕均分 100 份,1vw就是 1 份(以后要流行的方案)

去参考下手淘(作者)分享的解决方案
如何在Vue项目中使用vw实现移动端适配

使用媒体查询(@media screen)

  • 设计图尺寸:750x1334 px
  • rem计算方式:设计图尺寸px / 100 = 实际rem 【例: 100px = 1rem,50px = 0.50rem】
    在模板页面的css中添加以下代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
      html{
    font-size: 26.66667vw;
    }
    body {
    font-family: 'PingFang SC', Helvetica, 'STHeiti STXihei', 'Microsoft YaHei', Tohoma, Arial, sans-serif;
    background-color: #f8f8f8;
    font-size: 14px;
    color: #333;
    line-height: 1.4;
    min-width:320px;
    max-width:640px;
    margin-left:auto;
    margin-right:auto;
    }
    @media only screen and (max-width: 768px) {
    html {
    font-size:204.8px
    }
    }

    @media only screen and (max-width: 640px) {
    html {
    font-size:170.66667px
    }
    }

    @media only screen and (max-width: 480px) {
    html {
    font-size:128px
    }
    }

    @media only screen and (max-width: 414px) {
    html {
    font-size:110.4px
    }
    }

    @media only screen and (max-width: 412px) {
    html {
    font-size:109.86667px
    }
    }

    @media only screen and (max-width: 400px) {
    html {
    font-size:106.66667px
    }
    }

    @media only screen and (max-width: 393px) {
    html {
    font-size:104.8px
    }
    }

    @media only screen and (max-width: 375px) {
    html {
    font-size:100px
    }
    }

    @media only screen and (max-width: 360px) {
    html {
    font-size:96px
    }
    }

    @media only screen and (max-width: 345px) {
    html {
    font-size:92px
    }
    }

    @media only screen and (max-width: 320px) {
    html {
    font-size:85.33333px
    }
    }

其他

处理1像素边框

在rem方案中,如果没有特殊处理,1px的线条在 dpr 大于 1 的设备会比较粗。根本原因是1px可能会占用两个物理像素点或以上。
可以采用 transform: scale(0.5);来解决,在上面的selectorBlackList添加'.ui-hairline'

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.ui-hairline, .ui-hairline--bottom, .ui-hairline--left, .ui-hairline--right, .ui-hairline--surround, .ui-hairline--top, .ui-hairline--top-bottom {
position:relative;
}
.ui-hairline--bottom::after, .ui-hairline--left::after, .ui-hairline--right::after, .ui-hairline--surround::after, .ui-hairline--top-bottom::after, .ui-hairline--top::after, .ui-hairline::after {
content:'';
position:absolute;
top:0;
left:0;
width:200%;
height:200%;
-webkit-transform:scale(.5);
transform:scale(.5);
-webkit-transform-origin:0 0;
transform-origin:0 0;
pointer-events:none;
box-sizing:border-box;
border:0 solid #e5e5e5
}
.ui-hairline--top::after {
border-top-width:1px
}
.ui-hairline--left::after {
border-left-width:1px
}
.ui-hairline--right::after {
border-right-width:1px
}
.ui-hairline--bottom::after {
border-bottom-width:1px
}
.ui-hairline--top-bottom::after {
border-width:1px 0
}
.ui-hairline--surround::after {
border-width:1px
}

获取手机设备系统信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var util = {
getSystemInfo:function(){
var ua = window.navigator.userAgent.toLowerCase();
ua = ua.toLowerCase();
function a(ua) {
if (ua.indexOf(android) > -1) return 'android';
if (/iphone|ipad|ipod|ios/.test(UA)) return 'ios';
return 'unknown';
};
function isWeiXin(ua) {
return ua.indexOf('micromessenger') > -1
};
var obj = {
userAgent:window.navigator.userAgent,
pixelRatio: window.devicePixelRatio,
platform: a(ua),
language:window.navigator.language,
screenWidth: window.screen.width,
screenHeight: window.screen.height,
windowWidth: document.documentElement.clientWidth,
windowHeight: document.documentElement.clientHeight
};
return obj;
}
}
console.log(util.getSystemInfo())

util.getSystemInfo()返回参数说明

属性 类型 说明
userAgent String 用户代理,简称 UA
platform String 客户端平台:Android,iOS
pixelRatio Number 设备像素比
screenWidth Number 屏幕宽度,单位px
screenHeight Number 屏幕高度,单位px
windowWidth Number 可使用窗口宽度,单位px
windowHeight Number 可使用窗口高度,单位px
language String 语言

Tips

  • userAgent其实里面包含更多信息,例如手机型号,系统版本,
1
2
3
4
5
// 小米note3(手机型号:Mi Note 3,系统版本:Android 9)
var ua = "Mozilla/5.0 (Linux; Android 9; RMX1901 Build/PKQ1.190101.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/044705 Mobile Safari/537.36 MMWEBID/1437 MicroMessenger/7.0.4.1420(0x2700043C) Process/tools NetType/WIFI Language/zh_CN"

// iPhone 6 Plus(手机型号:unknown,系统版本:iPhone OS 12_3_1)
var ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15(KHTML, like Gecko) Mobile / 15E148 MicroMessenger / 7.0.4(0x17000428) NetType / WIFI Language / zh_CN"

不难发现,在 UA 中,可以看待安卓的手机型号一般在Build/前面,ios的还并没有发现