vue 响应式布局方案探索
最近我司准备使用 vue3 + vant
重构原本 vue2 + vux
的项目,并且需要适配平板,原本的项目中,我们使用 rem
布局,这次重构时,我们原本准备采用了 vw
的布局方式,但是设计图中,某些页面在平板竖版的时候,需要将页面固定宽度并且内容居中展示,如图,采用 vw
布局时,文字就会变得很大,一番权衡之下,还是采用了 rem
布局,并且在不同视口宽度下,采用不同的根标签大小适配。
那应该如何实现呢,首先,根据 vant 组件库推荐的 rem
布局,需要安装插件 postcss-potorem 和 lib-flexible 这个包,此时的 package.json
相关配置如下:
json
"dependencies": {
"amfe-flexible": "^2.2.1",
},
"devDependencies": {
"postcss-pxtorem": "^6.1.0",
}
在根目录下新建文件 postcss.config.cjs
文件,添加 postcss-pxtorem
配置,可以参考 vant 官网查看 rem布局适配
这里。
js
module.exports = {
map: true,
plugins: {
'postcss-pxtorem': {
rootValue: 37.5, // 此设计稿尺寸为375px
propList: ['*'],
mediaQuery: true
},
}
}
但是这并不能满足我的需求,即大于一定尺寸时宽度固定。所以我这里删除了依赖 amfe-flexible
,并且在 utils
文件夹目录下新建文件 amfe-flexible.js
文件,将 amfe-flexible
依赖里面的内容拿过来稍作修改,并且在 main.js
文件中引入,这里的意思是,当视口宽度大于 PAD_MIN_WIDTH
这个值时,将根标签的 font-size
固定,否则取 docEl.clientWidth / 10
之后的值赋值给根标签的font-size
。
js
const PAD_MIN_WIDTH=500
(function flexible (window, document) {
var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1
// adjust body font size
function setBodyFontSize () {
if (document.body) {
document.body.style.fontSize = (12 * dpr) + 'px'
}
else {
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
setBodyFontSize();
// set 1rem = viewWidth / 10
function setRemUnit () {
// 当前按照500px区分 手机 和PAD
// 手机时按尺寸缩放 PAD 时固定基础尺寸
if (docEl.clientWidth > PAD_MIN_WIDTH) {
docEl.style.fontSize = '37.5px'
return
}
const rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports
if (dpr >= 2) {
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
}(window, document))
此时基本已经完成了我所需要的功能,不过还有个小问题,就是在每个 vue
组件中,实际上样式基本都是添加了 scoped
属性的,如果需要写适配,则需要在每个组件中都写一遍媒体查询的设备尺寸的判断,如下这样:
css
<style scoped>
@media (max-width: 500px) {
/* 手机屏幕样式 */
}
@media (min-width: 501px) and (max-width: 1024px) {
/* 平板屏幕样式 */
}
</style>
如果设备尺寸的值变了,那么就需要一个个修改每个页面,所以这里准备使用一个变量代替当前位置写死的值,不过百度后发现设备尺寸这里并不支持使用变量的方式。
于是我接着百度了一下,最终找到了 postcss
的一个插件 PostCSS Custom Media ,这个插件是专门用来解决媒体查询中使用变量表达的,但是 ,又又又遇到了问题,在 vite
项目中使用时貌似这个插件不能识别,具体可以看这篇 issue ,顺着这篇文章,我发现了评论区有作者提供的解决办法。
If you're using Modular CSS such as, CSS Modules, postcss-loader or vanilla-extract to name a few, you'll probably notice that custom media queries are not being resolved... To overcome this, we recommend using the PostCSS Global Data plugin.
以上内容在这个issue里面 github.com/vitejs/vite...
于是,新插件 postcss-global-data
火速下载下来,npm install @csstools/postcss-global-data --save-dev
现在我的 package.json
相关的包时这样的。删除了 amfe-flexible
。
json
"devDependencies": {
"@csstools/postcss-global-data": "^2.1.1",
"postcss-pxtorem": "^6.1.0",
}
此时我的 postcss.config.cjs
文件中是这样的。
js
module.exports = {
map: true,
plugins: {
'@csstools/postcss-global-data': {
files: [
'./src/assets/style/custom-media.css'
]
},
'postcss-pxtorem': {
rootValue: 37.5, // 此设计稿尺寸为375px
propList: ['*'],
mediaQuery: true
},
}
}
./src/assets/style/custom-media.css
这个路径就是将媒体查询中的设备尺寸定义为变量的文件路径,内容如下:
css
/* 针对手机的媒体查询 */
@custom-media --phone (width <= 500px);
/* 针对PAD的媒体查询 */
@custom-media --pad (width > 500px);
/* 针对全屏PAD的媒体查询 */
@custom-media --pad-full (width > 900px);
但是,后来又遇到了问题,在我的电脑上运行的非常 nice ,同事的电脑上却没有正确的识别设备尺度的临界值,经过多次试错发现,只需要将 px
改为 PX
即可。
css
/* 针对手机的媒体查询 */
@custom-media --phone (width <= 500PX);
/* 针对PAD的媒体查询 */
@custom-media --pad (width > 500PX);
/* 针对全屏PAD的媒体查询 */
@custom-media --pad-full (width > 900PX);
这里定义了三个变量,在具体的vue文件中使用时,只需要这样写即可:
vue
<style scoped>
@media (--phone) {
/* 手机样式 */
}
@media (--pad) {
/* 平板样式 */
}
@media (--pad-full) {
/* 平板全屏样式 */
}
</style>
当然,媒体查询也可以判断设备是横屏还是竖屏,不过我司项目是嵌套在软件中使用,具体横屏竖屏由软件开发同事解决,所以这里并未写横屏的媒体查询,不过这个也不难,这样写即可。
css
@media (orientation: portrait) {
/* 设备为竖屏时的样式 */
}
@media (orientation: landscape) {
/* 设备为横屏时的样式 */
}
自此,响应式布局的全部配置搭建已完成,剩下的就是在不同的媒体查询中写样式了。