vue 响应式布局方案探索

vue 响应式布局方案探索

最近我司准备使用 vue3 + vant 重构原本 vue2 + vux 的项目,并且需要适配平板,原本的项目中,我们使用 rem 布局,这次重构时,我们原本准备采用了 vw 的布局方式,但是设计图中,某些页面在平板竖版的时候,需要将页面固定宽度并且内容居中展示,如图,采用 vw 布局时,文字就会变得很大,一番权衡之下,还是采用了 rem 布局,并且在不同视口宽度下,采用不同的根标签大小适配。

那应该如何实现呢,首先,根据 vant 组件库推荐的 rem 布局,需要安装插件 postcss-potoremlib-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) {  
  /* 设备为横屏时的样式 */  
}  
​

自此,响应式布局的全部配置搭建已完成,剩下的就是在不同的媒体查询中写样式了。

相关推荐
小远yyds18 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
程序媛小果37 分钟前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
小光学长42 分钟前
基于vue框架的的流浪宠物救助系统25128(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库·vue.js·宠物
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱1 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai1 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4113 小时前
无网络安装ionic和运行
前端·npm
理想不理想v3 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试