【Vue 移动端开发】适配百分之99的屏幕方案

简介: 【Vue 移动端开发】适配百分之99的屏幕方案

一、引入

之前提起移动端适配,都是一些视口的概念,包括物理像素和逻辑像素,理想视口,dpr等等等。利用 media query 和 rem 是最常见的移动端适配方。如下代码:

const deviceWidth = document.documentElement.clientWidth || document.body.clientWidth;
document.querySelector('html').style.fontSize = deviceWidth / 7.5 + 'px';

在移动端UI稿尺寸为 7501334 满天飞的时代,这两句代码确实给开发者带来了很大的方便,这样设置根font-size后,px 和 rem 的转换比例成了100, 为比如UI稿一个长宽分别为 120px40px,那么开发者对应的写成1.2rem*0.4rem就可以了。后面,甚至诞生了 px-to-rem 或者 px2rem 等开发插件来帮助我们更便捷的做出计算。

数字化管理平台

Vue3+Vite+VueRouter+Pinia+Axios+ElementPlus

Vue权限系统案例

个人博客地址

**flexible.js是手淘开发出的一个用来适配移动端的js框架。**手淘框架的核心原理就是根据制不同的width给网页中html根节点设置不同的font-size,然后所有的px都用rem来代替,这样就实现了不同大小的屏幕都适应相同的样式了。其实它就是一个终端设备适配的解决方案,也就是说它可以让你在不同的终端设备中实现页面适配。

VSCode 安装插件:

e6f7e8615e5f4886a54491cbb6f29f31.png

setting.json中修改cssrem中默认的字体大小,这里以屏幕为750px为例(因为flexible.js默认将屏幕分成十份,所以我们将cssrem.rootFontSize修改为75)

9691e10a3c54492ca1f1a5b08bef36b9.png

dbd95575c3b848058ad0f374888fa988.png

保存之后重新打开vscode

再当我们输入宽度的时候,我就可以看到vscode自动帮我们计算好了对应的rem值,我们只要点击使用就可以了。

f5427c1cc74c48528b0de8e08450f532.png

可是,随着技术的发展,人们希望有这样一种方案:不想换算,也不想考虑转换系数,更不想借助开发插件转换,就想简简单单的使用一种单位 px,使得 CSS 代码足够简洁。

postcss-px-to-viewport就是这样一款优秀的插件,它将 px 转换成视口单位 vw。众所周知,vw本质上还是一种百分比单位,100vw即等于100%,即 window.innerWidth。

vw 视口的最大宽度,1vw等于视口宽度的百分之一

vh 视口的最大高度,1vh等于视口高度的百分之一

插件使用

安装 postcss-px-to-viewport 插件

yarn add postcss-px-to-viewport -D

db17bc6631aa41d9b21840050a9ac439.png

注:Vite 中已经内联了 postcss,所以并不需要额外的创建 postcss.config.js文件。

Vite 项目,直接在 vite.config.js 中配置如下:

import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import pxtovw from "postcss-px-to-viewport" 
const loader_pxtovw = pxtovw({
   unitToConvert: 'px', // 要转化的单位
   viewportWidth: 750, // UI设计稿的宽度
   unitPrecision: 6, // 转换后的精度,即小数点位数
   propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
   viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
   fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
   selectorBlackList: ['ignore-'], // 指定不转换为视窗单位的类名,
   minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
   mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
   replace: true, // 是否转换后直接更换属性值
   // exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
   landscape: false // 是否处理横屏情况
})
export default defineConfig({
  plugins: [vue(), vueJsx()],
  css: {
    postcss: {
      plugins: [loader_pxtovw]
    }
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

propList: 当有些属性的单位我们不希望转换的时候,可以添加在数组后面,并在前面加上!号,如 propList: [“*”,“!letter-spacing”],这表示:所有css属性的属性的单位都进行转化,除了letter-spacing

selectorBlackList:转换的黑名单,在黑名单里面的我们可以写入字符串,只要类名包含有这个字符串,就不会被匹配。比如selectorBlackList: [‘wrap’],它表示形如wrap,my-wrap,wrapper这样的类名的单位,都不会被转换

使用 vant UI 库修改配置

我这里没有设计稿,并且使用了 vant UI库,所以根据官方设定 viewportWidth: 375,修改 vue.config.js 配置如下:

这里省略了其它配置参数:

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
// 适配移动端的插件
import pxtovw from "postcss-px-to-viewport"
// 如果是其他的文件,我们就按照我们UI的宽度来设置viewportWidth,即750。
const loder_pxtovw = pxtovw({
  viewportWidth: 750,
  viewportUnit: 'vw',
  exclude: [/node_modules\/vant/i]
})
// 如果读取的是vant相关的文件,viewportWidth就设为375。但是经过实践,vant部分组件需要兼容处理的地方比较多,这里先注释了
// 比如 <van-icon name="scan" class="sm"/> 组件不能通过文档提供的 size 属性配置大小,只能通过非行内的 css 显式 font-size 配置样式才能正常适配
// const vant_pxtovw = pxtovw({
//   viewportWidth: 375,
//   viewportUnit: 'vw',
//   exclude: [/^(?!.*node_modules\/vant)/] //忽略除vant之外的
// })
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), vueJsx()],
  css: {
    postcss: {
      plugins: [
        // vant_pxtovw,
        loder_pxtovw
      ]
    }
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

页面组件代码

<template>
  <div class="wraps">
    <!-- 头部 -->
    <van-row align="center" class="header">
      <van-col span="4">
        <span>北京</span>
        <van-icon name="location-o" />
      </van-col>
      <van-col span="17">
        <van-search v-model="searchVal" placeholder="请输入搜索关键词" />
      </van-col>
      <van-col span="3">
        <van-icon name="scan" size="30px" />
      </van-col>
    </van-row>
    <!-- 列表 -->
    <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad" style="margin-top:60px">
      <van-cell v-for="item in list" :key="item" :title="item" />
    </van-list>
    <van-tabbar v-model="active">
      <van-tabbar-item icon="home-o">首页</van-tabbar-item>
      <van-tabbar-item icon="search">发现</van-tabbar-item>
      <van-tabbar-item icon="friends-o">消息</van-tabbar-item>
      <van-tabbar-item icon="setting-o">我的</van-tabbar-item>
    </van-tabbar>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue';
const searchVal = ref('')
const active = ref(0);
const list = ref([]);
const loading = ref(false);
const finished = ref(false);
const first = ['侯', '赵', '李', '刘', '马', '张']
const second = ['林', '梓', '梦', '景', '晓', '晨']
const third = ['汐', '烨', '畅', '男', '涵', '辰']
const getRanIndex = () => Number.parseInt(Math.random() * 6)
const onLoad = () => {
  // 异步更新数据
  // setTimeout 仅做示例,真实场景中一般为 ajax 请求
  setTimeout(() => {
    for (let i = 0; i < 100; i++) {
      list.value.push(first[getRanIndex()] + second[getRanIndex()] + third[getRanIndex()] + '--------' + (list.value.length + 1));
    }
    // 加载状态结束
    loading.value = false;
    // 数据全部加载完成
    if (list.value.length >= 40) {
      finished.value = true;
    }
  }, 1000);
};
</script>
<style scoped lang="less">
.header {
  position: fixed;
  top: 0;
  width: 100vw;
  z-index: 1000;
  box-shadow: 0 2px 1px 3px #f7f4f4;
  text-align: center;
  background-color: #fff;
}
</style>

页面效果

84a671e359af49edb58ae55643ef93ed.png

004d92aefba84f1084e81f165161d805.png

e3938479e272467ea2b23d57fc50c17f.png

7839df820fab45f0825b6d4ad78bfff9.png

三、补充一

如果是 Webpack ,则需要在项目根目录下添加.postcssrc.js 文件进行如下配置:

const path = require('path');
module.exports = ({ webpack }) => {
  // 如果读取的是vant相关的文件,viewportWidth就设为375,如果是其他的文件,我们就按照我们UI的宽度来设置viewportWidth,即750。
  const designWidth = webpack.resourcePath.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,
        // 我们引入一些第三方库的时候,比如 vant,上面配置的 exclude 去掉(注释也可),表示全部内容进行 vw 转换
        exclude: [],
        landscape: false
      }
    }
  }
}

vant团队的是根据375px的设计稿去做的,理想视口宽度为375px。所以做了如上判断

这里使用 path.join(‘node_modules’, ‘vant’) 是因为适应不同的操作系统:在 mac 下结果为 node_modules/vant,而在 windows下结果为 node_modules\vant。

vant 官方适配提醒截图:

e44b3ec2be5249269138d25d5cc55b81.png

da6bbeccfc0c47ba8f37c4e243a2c1ae.png

四、补充二

如果项目集成了 TS,需要给这个插件提供声明文件。否则直接从 postcss-px-to-viewport 依赖中读取内容,路径会爆红,并且 postcsspxtoviewport 中也没有智能提示,这是因为缺少声明文件。

785965cb723b45b1acc2aab56c390674.png

手写 postcss 类型声明文件 postcss-px-to-viewport.d.ts ,解决 路径爆红、没有提示的问题

通过 declare module ‘postcss-px-to-viewport’ 给 postcss 扩充声明 如下:

declare module 'postcss-px-to-viewport' {
    type Options = {
        unitToConvert: 'px' | 'rem' | 'cm' | 'em',
        viewportWidth: number,
        // 下面的不常用,上面的常用
        viewportHeight: number, // 目前未使用;TODO:需要不同的单位和不同属性的数学
        unitPrecision: number,
        viewportUnit: string,
        fontViewportUnit: string,  // vmin更适合
        selectorBlackList: string[],
        propList: string[],
        minPixelValue: number,
        mediaQuery: boolean,
        replace: boolean,
        landscape: boolean,
        landscapeUnit: string,
        landscapeWidth: number
    }
    // 注意:这里导出一个函数,如果使用箭头函数容易报错,推荐使用 function 这种写法
    export default function(options: Partial<Options>):any
}

类型声明文件注意问题:

类型声明文件,最终导出一个函数

如果使用箭头函数容易报错,推荐使用 function 这种写法

Partial 可以让 ts 中的属性全部变成可选项(非必填项)

postcss 是针对 vite 的工具,因此要在 tsconfig.config.json 中引入手写的声明文件

{
  "extends": "@vue/tsconfig/tsconfig.web.json",
  "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "postcss-px-to-viewport.d.ts"],
  "exclude": ["src/**/__tests__/*"],
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

tsconfig.config.json —— 用于配置 vite 需要的各种工具

tsconfig.json —— 用于配置 Vue 需要的各种工具



相关文章
|
1天前
|
资源调度 JavaScript 前端开发
【vue】vue中的路由vue-router,vue-cli脚手架详细使用教程
【vue】vue中的路由vue-router,vue-cli脚手架详细使用教程
|
1天前
|
JavaScript 前端开发
vue组件化开发流程梳理,拿来即用
vue组件化开发流程梳理,拿来即用
|
1天前
|
JavaScript Go
Vue路由跳转及路由传参
Vue路由跳转及路由传参
|
2天前
|
JavaScript 前端开发 BI
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
开发环境 技术架构:前后端分离 开发语言:C#.net6.0 开发工具:vs2022,vscode 前端框架:Vue,Ant-Design 后端框架:百小僧开源框架 数 据 库:sqlserver2019
采用前后端分离Vue,Ant-Design技术开发的(手麻系统成品源码)适用于三甲医院
|
5天前
|
JavaScript
【vue】如何跳转路由到指定页面位置
【vue】如何跳转路由到指定页面位置
7 0
|
5天前
|
JSON JavaScript 前端开发
【vue】假数据的选择和使用
【vue】假数据的选择和使用
11 1
|
5天前
|
JavaScript 前端开发
【vue】iview如何把input输入框和点击输入框之后的边框去掉
【vue】iview如何把input输入框和点击输入框之后的边框去掉
12 0
|
4天前
|
监控 JavaScript
Vue中的数据变化监控与响应——深入理解Watchers
Vue中的数据变化监控与响应——深入理解Watchers
|
4天前
|
JavaScript 安全 前端开发
Vue 项目中的权限管理:让页面也学会说“你无权访问!
Vue 项目中的权限管理:让页面也学会说“你无权访问!
13 3
|
4天前
|
JavaScript 前端开发 开发者
Vue的神奇解锁:冒险的开始
Vue的神奇解锁:冒险的开始
5 1