背景
作为前端开发, UI 设计师给出的设计稿单位都是 px,而移动端开发时,需要适配不同的界面宽度,兼容不同机型。
本文介绍采用通过配置后,在开发中可以直接使用设计图的尺寸开发,项目为我们自动编译,转换单位。
本文主要介绍2种方式,rem 方式和vw 方式,并详细解释其中的原理。
基础知识回顾
先回顾一些 CSS 的基础知识,熟悉的同学可以直接跳过。
css 单位
1、px: 像素单位,固定单位,不会根据其他元素的大小或用户设置而改变
特点:
- 绝对单位:px 是一个固定单位,不会根据其他元素的大小或用户设置而改变。
- 使用场景:适用于需要精确控制尺寸的场合,如边框、图像大小等。
2、em:相对单位,基于当前元素的字体大小。
特点:
- 相对单位:1em 等于当前元素的字体大小。
- 继承性:em 会继承父元素的字体大小,因此在嵌套元素中,em 的值可能会累积。
任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1em=16px。那么12px=0.75em,10px=0.625em
为了简化font-size的换算,需要在css中的body选择器中声明 Font-size=62.5%,这就使em值变为 16px*62.5%=10px, 这样12px=1.2em, 10px=1em, 也就是说只需要将你的原来的px数值除以10,然后换上em作为单位就行了。
3、rem:相对单位,基于根元素()的字体大小。
特点:
- 相对单位:1rem 等于根元素的字体大小。
- 不继承:与 em 不同,rem 不会累积,始终基于根元素。
4、vh vw:相对于视口的高度 和宽度。1vh 等于1/100的视口高度,1vw 等于1/100的视口宽度。
例如:浏览器高度900px,宽度为750px, 1 vh = 900px/100 = 9 px,1vw = 750px/100 = 7.5 px
100%和100vh/vw的区别:% 是相对于父元素的大小设定的比率。vw (viewport width) vh (viewport height) 是视窗的大小,100vw = 100%视窗宽度 100vh = 100%视窗高度,用vw,vh设定的大小只和视窗大小有关,和他们的父元素高度宽度没关系
CSS 像素、物理像素
CSS像素: 网页设计中使用的单位,通常是我们在CSS中设置的宽度、高度等。
物理像素: 设备屏幕上的实际像素点。
设备像素比(devicePixelRatio) :物理像素与CSS像素的比率。高分辨率设备(如Retina显示屏)通常有较高的DPR,比如2或3。
例如,我们在代码中设置一个 div 的宽度是 1px,在 DPR 是2的设备时,会显示为2个物理像素点。
在JavaScript中,可以通过window.devicePixelRatio
获取到当前设备的dpr。
在CSS中,可以通过-webkit-device-pixel-ratio
,-webkit-min-device-pixel-ratio
和 -webkit-max-device-pixel-ratio
进行媒体查询。
meta[viewport] 标签
可以控制视口的大小和比例,告诉浏览器如何规范的渲染Web页面
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
其中,content
参数有以下几种:
value | 可能值 | 描述 |
---|---|---|
width | 1-10000 或 device-width | 正整数:以pixels(像素)为单位, 定义viewport(视口)的宽度 device-width: 100vw,100% 的视口宽度 |
height | 1-10000 或 device-height | 正整数:以pixels(像素)为单位, 定义viewport(视口)的高度 device-height: 100vh,100% 的视口高度 |
initial-scale | 0.1 - 10 | 默认1。控制页面首次加载时显示的缩放倍数 |
maximum-scale | 0.1 - 10 | 默认1。控制页面允许放大的倍数。设置一个低于 3 的值将不具备无障碍访问性 |
minimum-scale | 0.1 - 10 | 默认1。控制页面允许缩小的倍数 |
user-scalable | 0 、1 、yes 或 no | 控制是否允许页面上的放大和缩小操作。 0、no:不允许 1、yes:允许 |
rem 方式
设置根节点 font-size
为什么设置根节点 font-size
从前文的基础知识可以了解到,使用rem为元素设定字体大小时,相对的是HTML根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。
所以当我们根据设备的分辨率,来动态设置根节点 font-size 以后,那么页面中所有单位是 rem 的都会自动调整了。
如何设置根节点 font-size
-
引入 lib-flexible
npm i lib-flexible
-
在 main.js 入口处引入
import 'lib-flexible';
-
启动项目,看根节点是否已经有 font-size 了
lib-flexible 解析
第一步,获取 dpr
。优先从 meta[viewport] 标签读取 initial-scale
,生成 dpr,如果没有 meta[viewport],就根据 window.devicePixelRatio
等生成 dpr,并插入 meta[viewport] 标签。
从 meta[viewport] 标签读取:
const metaEl = document.querySelector('meta[name="viewport"]');
let dpr = 0;
let scale = 0;
if (metaEl) {
const match = metaEl.getAttribute('content').match(/initial-scale=([\d.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
}
移动端一般会在 html 的head 中添加如下标签,其中 initial-scale
控制页面首次加载时显示的缩放倍数。
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no />
所以这一段的目的,其实就是看页面的开发者是否依据了网页的内容最佳展示,设置了缩放倍数,如果有的话,就把它取出来,得到后问需要的 scale
和 dpr
。
但如果网页的开发者并没有设置这个标签呢,也会通过 window.devicePixelRatio
做适配,可以看到只针对 iPhone 做了处理,针对 Android 或其他设备,都统一用了 1 处理
if (!dpr && !scale) {
const isIPhone = window.navigator.appVersion.match(/iphone/gi);
const devicePixelRatio = window.devicePixelRatio || 1;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}
如果没有 meta 标签,也会插入一个meta 标签
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
第二步,根据 dpr 和 页面宽度,设置根节点font-size
function refreshRem(){
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
针对 540的限制,我也查了相关资料,以下是作者解答😂。关于页面宽度更好的解决方案,目前还没有找到。总之,记住根节点字体大小是页面宽度的 1/10,比如页面宽度是460px,则根节点 font-size 设置为 46px;页面宽度是540px,则根节点 font-size 设置为 54px;页面宽度是600px,则根节点 font-size 设置为 54px。
这里添加一个小插曲,解释下 getBoundingClientRect().width 和 document.documentElement.clientWidth 的区别
getBoundingClientRect().width document.documentElement.clientWidth 返回元素的边框矩形的宽度,包括滚动条的宽度(如果有的话) 返回文档视口的宽度,不包括滚动条的宽度 返回的是一个浮点数,可能包含小数部分 返回的是一个整数 计算的是元素在视口中的实际显示宽度,受 CSS 变换(如缩放、旋转等)影响,如果有缩放,获取的是缩放后的宽度 仅考虑视口的宽度,不受 CSS 变换影响。如果有缩放,获取的仍然是原来的宽度
第3步,在页面调整时,重新执行设置根节点 font-size
win.addEventListener('resize', function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener('pageshow', function(e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
自动转换 px 为 rem
经过以上的设置,已经在根节点添加了 font-size,接下来需要做的就是编译时,实现自动转换 px 为 rem。
如何设置
1、安装插件
npm install postcss-pxtorem autoprefixer -D
2、配置 postcss.config.js
const autoprefixer = require('autoprefixer');
const pxtorem = require('postcss-pxtorem');
module.exports = {
plugins: [
pxtorem({ rootValue: 75, unitPrecision: 5, propList: ['*'] }),
autoprefixer({ browsers: ['Android >= 4.0', 'iOS >= 7'] }),
]
};
3、通过以下命令构建的vue应用在编译时会自动查找配置文件 postcss.config.js
(内部基于 webpack 配置)
如果没有通过以下命令构建应用,请自行查阅如何配置 postcss 使用
npm create vue@latest # 注意去掉 package.json 中 type 配置
4、启动,在控制台可以看到 根节点有 font-size
,并且 px 已经被自动转为 rem了
npm run dev
pxtorem 解析
function createPxReplace(rootValue, unitPrecision, minPixelValue) {
return (m, $1) => {
if (!$1) return m;
const pixels = parseFloat($1);
if (pixels < minPixelValue) return m;
const fixedVal = toFixed(pixels / rootValue, unitPrecision);
return fixedVal === 0 ? "0" : fixedVal + "rem";
};
}
关注关键逻辑,可以看出,使用了配置的 rootValue 属性。
例如:项目代码中写的了 height: 150px
,而 pxtorem
配置的 rootValue
为75,则编译后 height: 2rem
。
解释 rootValue
配置
实际就是1rem等于多少px的转换单位,默认是75,可以按照实际项目使用的 UI 设计稿的尺寸配置。
和前面的 font-size 结合理解一下,前文的根节点 font-size,我们是按照页面宽度的 1/10 设置的,那么举个例子说明:
IPhone14 Pro Max 尺寸:430
// html
font-size: 43px;
UI 设计稿:
查看设计稿选择页面宽度:750px 2倍
且设计稿展示的某卡片高度:150 px
那么我们将 pxtorem
的 rootValue 设置为 75:
pxtorem({ rootValue: 75 })
则我们在编写代码时候,这个卡片的高度,就直接按照设计稿的尺寸书写:
// css
height:150 px;
经过编译后:150 / 75
height:2rem;
rem 方式总结
1、根节点设置 font-size
2、添加 pxtorem
,编译时,自动将 px 转换为 rem 单位
3、页面开发时,根据 UI 设计师给出的尺寸,直接书写 px 开发
vw 布局方式
通过上文 rem 方式,相信大家也能看到 rem 的方式实现存在一些问题,比如设置font-size 的时候,作者使用了540作临时限制,导致在一些场景下是没法实现自适应兼容的。所以介绍第2种方式 vw 单位。
原理
目前UI设计师出设计稿,基本使用750px
宽度的,那么100vw = 750px
,即1vw = 7.5px
。那么可以根据设计图上的px
值直接转换成对应的vw
值。在实际写代码过程,不需要进行任何的计算,直接在代码中写px
,比如:
.test {
border: .5px solid black;
border-bottom-width: 4px;
font-size: 14px;
line-height: 20px;
position: relative;
}
[w-188-246] {
width: 188px;
}
编译出来的CSS:
.test {
border: .5px solid #000;
border-bottom-width: .533vw;
font-size: 1.867vw;
line-height: 2.667vw;
position: relative;
}
[w-188-246] {
width: 25.067vw;
}
如何设置
1、安装插件
npm install postcss-px-to-viewport --save-dev
2、配置 postcss.config.js
const autoprefixer = require('autoprefixer');
const pxToViewport = require('postcss-px-to-viewport');
module.exports = {
plugins: [
autoprefixer({ browsers: ['Android >= 4.0', 'iOS >= 7'] }),
pxToViewport({
viewportWidth: 750, // 参考的设计稿的尺寸
unitPrecision: 5,
}),
]
};
3、启动,在控制台可以看到 px 已经被自动转为 vm 了