主角是誰
在今天這篇文章中,我并不會(huì)在這里講一些移動(dòng)端視口的概念,包括物理像素和邏輯像素,理想視口,dpr等等等等,我只介紹這樣一種非常不錯(cuò)的移動(dòng)端適配方案:post-css-to-viewport
痛點(diǎn)在哪里
在之前有一種流行已久的移動(dòng)端適配方案,那就是rem,我想下面這兩句代碼,有不少老移動(dòng)端都不會(huì)陌生:
const deviceWidth = document.documentElement.clientWidth || document.body.clientWidth;
document.querySelector('html').style.fontSize = deviceWidth / 7.5 + 'px';
沒錯(cuò),在那個(gè)移動(dòng)端UI稿尺寸為750*1334滿天飛的時(shí)代,這兩句代碼確實(shí)給開發(fā)者帶來了很大的方便,這樣設(shè)置根font-size后,px和rem的轉(zhuǎn)換比例成了100, 為比如UI稿一個(gè)長(zhǎng)寬分別為120px*40px,那么開發(fā)者對(duì)應(yīng)的寫成1.2rem*0.4rem就可以了
這種換算已經(jīng)是頗為方便,但是并非所有的項(xiàng)目都能這樣去設(shè)置一個(gè)方便換算的比例系數(shù),當(dāng)比例系數(shù)為100時(shí),小數(shù)點(diǎn)往前面挪兩位就行了,然而有的項(xiàng)目設(shè)置的換算系數(shù)千奇百怪,有50的,有16的,很多已經(jīng)嚴(yán)重超出口算力所能及的范疇了。所以后來誕生的px-to-rem或者px2rem就是為了解決這個(gè)問題
人們希望有這樣一種方案...
- 首先,無論換算方不方便,我都不想換算(就是這么懶??),我也不想去操心什么轉(zhuǎn)換系數(shù)
- 其次,有些屬性或者類選擇器我不想進(jìn)行轉(zhuǎn)換
- css代碼要足夠簡(jiǎn)潔,我只希望看到一種單位,那就是px
兩種方案都很好,但我偏愛后者
第一種方案是lib-flexible+postcss-pxtorem,在相當(dāng)長(zhǎng)一段時(shí)間里,這兩個(gè)插件搭配都是解決移動(dòng)端布局的神器,lib-flexible是阿里手淘系開源的一個(gè)庫(kù),用于設(shè)置font-size,同時(shí)處理一些窗口縮放的問題。其中一位主要貢獻(xiàn)者正是阿里的大神winter。
直到2020年的今天,我仍然可以說,lib-flexible+postcss-pxtorem是解決移動(dòng)端布局的主流,但是我們可以好好想一想,它是否有什么不足?
從我個(gè)人來說,我認(rèn)為它主要有以下兩個(gè)不足:
- 兩個(gè)插件需要配套使用,而且rootValue設(shè)置的值不好理解
- rem是相對(duì)于html元素字體單位的一個(gè)相對(duì)單位,從本質(zhì)上來說,
它屬于一個(gè)字體單位,用字體單位來布局,并不是太合適
翻閱其github地址,可以看到這樣一段有意思的話:

第二種方案是viewport,postcss-px-to-viewport就是這樣一款優(yōu)秀的插件,它解決了以上提到的痛點(diǎn),也滿足以上提到的理想要求。它將px轉(zhuǎn)換成視口單位vw,眾所周知,vw本質(zhì)上還是一種百分比單位,100vw即等于100%,即window.innerWidth
在vue項(xiàng)目中引入試試
- 我們先把它安裝到項(xiàng)目的開發(fā)環(huán)境中:
npm i postcss-px-to-viewport -D
- 在項(xiàng)目根目錄下添加
.postcssrc.js文件 - 添加如下配置:
module.exports = {
plugins: {
autoprefixer: {}, // 用來給不同的瀏覽器自動(dòng)添加相應(yīng)前綴,如-webkit-,-moz-等等
"postcss-px-to-viewport": {
unitToConvert: "px", // 要轉(zhuǎn)化的單位
viewportWidth: 750, // UI設(shè)計(jì)稿的寬度
unitPrecision: 6, // 轉(zhuǎn)換后的精度,即小數(shù)點(diǎn)位數(shù)
propList: ["*"], // 指定轉(zhuǎn)換的css屬性的單位,*代表全部css屬性的單位都進(jìn)行轉(zhuǎn)換
viewportUnit: "vw", // 指定需要轉(zhuǎn)換成的視窗單位,默認(rèn)vw
fontViewportUnit: "vw", // 指定字體需要轉(zhuǎn)換成的視窗單位,默認(rèn)vw
selectorBlackList: ["wrap"], // 指定不轉(zhuǎn)換為視窗單位的類名,
minPixelValue: 1, // 默認(rèn)值1,小于或等于1px則不進(jìn)行轉(zhuǎn)換
mediaQuery: true, // 是否在媒體查詢的css代碼中也進(jìn)行轉(zhuǎn)換,默認(rèn)false
replace: true, // 是否轉(zhuǎn)換后直接更換屬性值
exclude: [/node_modules/], // 設(shè)置忽略文件,用正則做目錄名匹配
landscape: false // 是否處理橫屏情況
}
}
};
- 重新運(yùn)行項(xiàng)目,使配置文件生效
- 我們寫一段測(cè)試代碼來驗(yàn)證一下:
<template>
<div class="test-viewport">測(cè)試轉(zhuǎn)換</div>
</template>
<style lang="less" scoped>
.test-viewport {
width: 750px;
height: 100px;
font-size: 40px;
text-align: center;
line-height: 100px;
background: #13b5b1;
}
</style>
- 打開控制臺(tái),查看是否已經(jīng)進(jìn)行了轉(zhuǎn)換
需要注意的配置
-
propList: 當(dāng)有些屬性的單位我們不希望轉(zhuǎn)換的時(shí)候,可以添加在數(shù)組后面,并在前面加上!號(hào),如propList: ["*","!letter-spacing"],這表示:所有css屬性的屬性的單位都進(jìn)行轉(zhuǎn)化,除了letter-spacing的 -
selectorBlackList:轉(zhuǎn)換的黑名單,在黑名單里面的我們可以寫入字符串,只要類名包含有這個(gè)字符串,就不會(huì)被匹配。比如selectorBlackList: ['wrap'],它表示形如wrap,my-wrap,wrapper這樣的類名的單位,都不會(huì)被轉(zhuǎn)換
關(guān)于兼容第三方UI庫(kù)
當(dāng)然,當(dāng)我們引入一些第三方庫(kù)的時(shí)候,比如vant,上面配置的exclude去掉,表示全部?jī)?nèi)容進(jìn)行vw轉(zhuǎn)換,會(huì)遇到這樣的問題:

像這個(gè)TabBar,變得非常的小,被壓扁了。
其實(shí)vant官網(wǎng)也是給出了關(guān)于viewport的適配方案,在github找一個(gè)名為vant-demo的項(xiàng)目,可以看到其配置如下:

很尷尬,vant團(tuán)隊(duì)的是根據(jù)375px的設(shè)計(jì)稿去做的,理想視口寬度為375px。
那么,我們是否也要叫UI重新出一版375px的設(shè)計(jì)稿?
或者,我們?cè)跁鴮懙倪^程心算一下,所有標(biāo)注的尺寸都除以2?
讓我們回到webpack本身,重新看一下這份.postcssrc.js文件,它除了暴露一個(gè)對(duì)象,也可以暴露一個(gè)函數(shù),無論暴露什么,在webpack運(yùn)行時(shí),都會(huì)被我們配置的海量文件讀取并執(zhí)行。
暴露函數(shù)有一個(gè)好處,可以拿到webpack運(yùn)行的當(dāng)前執(zhí)行文件的信息。
那么我們可以有這樣一個(gè)思路:如果讀取的是vant相關(guān)的文件,viewportWidth就設(shè)為375,如果是其他的文件,我們就按照我們UI的寬度來設(shè)置viewportWidth,即750。
改寫.postcssrc.js文件配置如下:
const path = require('path');
module.exports = ({ file }) => {
const designWidth = file.dirname.includes(path.join('node_modules', 'vant')) ? 375 : 750;
return {
plugins: {
autoprefixer: {},
"postcss-px-to-viewport": {
unitToConvert: "px",
viewportWidth: designWidth,
unitPrecision: 6,
propList: ["*"],
viewportUnit: "vw",
fontViewportUnit: "vw",
selectorBlackList: [],
minPixelValue: 1,
mediaQuery: true,
exclude: [],
landscape: false
}
}
}
}
注意:這里使用path.join('node_modules', 'vant')是因?yàn)檫m應(yīng)不同的操作系統(tǒng),在mac下結(jié)果為node_modules/vant,而在windows下結(jié)果為node_modules\vant
重新運(yùn)行后發(fā)現(xiàn),不僅vant相關(guān)組件的單位被轉(zhuǎn)換成了vw,而且其比例也是完全正確的。
