[Vue mobile terminal development] Adapt to 99% of the screen solution

1. Introduction

Before mentioning mobile terminal adaptation, there are some concepts of viewport, including physical pixels and logical pixels, ideal viewport, dpr and so on. Using media query and rem is the most common mobile adaptation solution . The following code:

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

In the era when the mobile UI draft size is 750 1334 flying all over the sky, these two codes really bring great convenience to developers. After setting the root font-size, the conversion ratio of px and rem becomes 100, for example, UI The length and width of a manuscript are 120px and 40px respectively, so the developer can write it as 1.2rem*0.4rem. Later, development plug-ins such as px-to-rem or px2rem were even born to help us make calculations more conveniently.

Digital management platform
Vue3+Vite+VueRouter+Pinia+Axios+ElementPlus
Vue permission system case
personal blog address

**flexible.js is a js framework developed by Taobao to adapt to the mobile terminal. **The core principle of the Taobao framework is to set different font-sizes for the html root node in the webpage according to different widths, and then replace all px with rem, so that screens of different sizes can adapt to the same style up. In fact, it is a terminal device adaptation solution, that is to say, it allows you to implement page adaptation in different terminal devices.

VSCode installation plug-in:
insert image description heremodify the default font size in cssrem in setting.json, here we take the screen as 750px as an example (because flexible.js divides the screen into ten parts by default, so we modify cssrem.rootFontSize to 75) save and
insert image description hereinsert image description here
reopen
When vscode enters the width, I can see that vscode automatically calculates the corresponding rem value for us, and we only need to click to use it.
insert image description here

However, with the development of technology, people hope to have such a solution: they don't want to convert, don't want to consider the conversion factor, and don't want to use the development plug-in to convert, but simply use a unit of px to make the CSS code simple enough.

postcss-px-to-viewport is such an excellent plugin, which converts px to viewport unit vw. As we all know, vw is essentially a percentage unit, 100vw is equal to 100%, that is, window.innerWidth.

  • vw The maximum width of the viewport, 1vw is equal to one percent of the viewport width
  • vh The maximum height of the viewport, 1vh is equal to one percent of the viewport height

plug-in use

Install the postcss-px-to-viewport plugin

yarn add postcss-px-to-viewport -D

Node version v14.18.1
insert image description here

Note: postcss has been inlined in Vite, so there is no need to create additional postcss.config.js file.

For Vite projects, configure directly in vite.config.js as follows:

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: When we do not want to convert the units of some attributes, we can add them after the array and add ! in front, such as propList: [“*”, “!letter-spacing”], which means: all css attributes The units of the attributes are converted, except letter-spacing
  • selectorBlackList: Converted blacklist, we can write strings in the blacklist, as long as the class name contains this string, it will not be matched. For example, selectorBlackList: ['wrap'], which means that the unit of the class name such as wrap, my-wrap, wrapper will not be converted

Use the vant UI library to modify the configuration
I don’t have a design draft here, and I use the vant UI library, so according to the official setting viewportWidth: 375, modify the configuration of vue.config.js as follows:
Other configuration parameters are omitted here:

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))
    }
  }
})

page component code

<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>

page effect
insert image description here
insert image description here
insert image description here
insert image description here

3. Supplement 1

If it is Webpack, you need to add the .postcssrc.js file in the project root directory for the following configuration:

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
      }
    }
  }
}
  1. The vant team is based on the design draft of 375px, and the ideal viewport width is 375px. So I made the above judgment
  2. The reason for using path.join('node_modules', 'vant') here is to adapt to different operating systems: the result is node_modules/vant under mac, and the result is node_modules\vant under windows.

Vant official adaptation reminder screenshot:
insert image description here
insert image description here

4. Supplement 2

If the project integrates TS, you need to provide a declaration file for the plugin. Otherwise, read the content directly from the postcss-px-to-viewport dependency, the path will explode, and there is no smart prompt in postcssspxtoviewport, because the declaration file is missing.
insert image description here

Hand-write the postcss type declaration file postcss-px-to-viewport.d.ts to solve the problem that the path is popular and there is no prompt. The
postcss extension declaration is as follows through declare module 'postcss-px-to-viewport':

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
}

Notes on type declaration files:

  • type declaration file, which eventually exports a function
  • If it is easy to report errors when using arrow functions, it is recommended to use function.
  • Partial can make all attributes in ts optional (non-required)

postcss is a tool for vite, so you need to introduce a handwritten declaration file in 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 - used to configure various tools needed by vite
tsconfig.json - used to configure various tools needed by Vue

Guess you like

Origin blog.csdn.net/qq_39335404/article/details/130372802