Vue3 + Vite + H5移动端适配

2,336 阅读3分钟

前言

主要是用vw来实现的,1vw相当于100视窗宽度,设计的宽度是375px,那么1vw就是3.75像素了。

一、设置meta标签

   <meta name="viewport" content="width=device-width, initial-scale=1.0">

二、配置适配plugin

1.在根目录传一个一个plugins文件

2.创建一个postcss-px-to-viewport.ts和一个types.ts

image.png

三、postcss-px-to-viewport.ts

/** 编写 postcss 插件 vite内置了postCss 无需安装 */
import { Plugin } from 'postcss'
import { type Options } from './types'

const Option = {
  /** UI设计稿的宽度 给多少写多少 一般默认375 */
  viewPortWidth: 375,
}
export const PostCssPxToViewport = (options: Options = Option): Plugin => {
    const opt = Object.assign({}, Option, options)
    return {
        postcssPlugin: 'postcss-px-to-viewport',
        
        /** 拿到所有css节点的钩子函数 */
        Declaration(node) {
            const value = node.value
            
            if (value.includes('px')) {
                /** 考虑到小数 */
                const num = parseFloat(value)
                
                /** 把px转换成vw再重新赋值给css节点 */
                node.value = `${((num / opt.viewPortWidth) * 100).toFixed(2)}vw`
            }
        }

    }
}

/** types.ts */
export type Options = {
  viewPortWidth?: number
}

四、在tsconfig.node.json配置plugins

image.png

五、在vite.config.ts配置plugins

image.png

前面步骤完成之后 我们看一下效果,可以自适应调整看效果(本人不会搞gif,所以就截图了)。 前面的步骤是百度参考的。

乍一看可以了,开心的很。

image.png

可是刚开心没多久就发现问题了,当我写一个 padding: 0 20px 20px;样式的时候 发现为padding被转换为0了,咦!这是咋回事呢?

image.png

既然有问题那么咱们就是log打印康康,看了发现除了类似padding: 20px;的才正常。既然发现问题了那么我们就好去解决问题了

/** 拿到所有css节点的钩子函数 */
      Declaration(node) {
          const value = node.value

          if (value.includes('px')) {
              /** 考虑到小数 */
              const num = parseFloat(value)
              console.log('value', value)
              console.log('num', num)

              /** 把px转换成vw再重新赋值给css节点 */
              node.value = `${((num / opt.viewPortWidth) * 100).toFixed(2)}vw`
          }
      }

image.png

六、分析一下怎么去处理上面截图转换失败的

    1.我们写一个正则去匹配value中带有px的
    2.通过上面匹配得出的数组我们再去遍历这个数组把px转换成vw
    3.再把转换的值组成和我们想要的值(padding: 16px 20px; => padding: xxvw xxvw;)再这个值赋值给node.value

1.我们写出匹配带有px的正则

    let str = '0 20px 20px'
    const matches = str.match(/(\d+(\.\d+)?)\s*px/g) || []
    console.log('matches', matches) => ['20px','20px']
    
    let str = 'transformY(-14px)'
    const matches = str.match(/(\d+(\.\d+)?)\s*px/g) || []
    console.log('matches', matches) => ['14px']

2.我们写一个辅助函数去转换匹配到的值

    /**
     * 转换px辅助函数
     * @param originalString 字符串来源
     * @param searchString 要被替换的值
     * @param replaceWith 替换成什么值
     * @returns
     */
    const transfromValue = (
      originalString: string,
      searchString: string,
      replaceWith: string
    ) => {
      if (originalString.includes(searchString)) {

        /** 转义正则表达式特殊字符 */
        const escapedSearchString = searchString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
        const regex = new RegExp(escapedSearchString, 'g')
        const newString = originalString.replace(regex, replaceWith)

        return newString;
      }

      /** 如果没有包含,则返回原始字符串 */
      return originalString;
    }

3.再把这个值赋值给node.value

    /**
     * 转换px辅助函数
     * @param originalString 字符串来源
     * @param searchString 要被替换的值
     * @param replaceWith 替换成什么值
     * @returns
     */
    const transfromValue = (
      originalString: string,
      searchString: string,
      replaceWith: string
    ) => {
      if (originalString.includes(searchString)) {

        /** 转义正则表达式特殊字符 */
        const escapedSearchString = searchString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
        const regex = new RegExp(escapedSearchString, 'g')
        const newString = originalString.replace(regex, replaceWith)

        return newString;
      }

      /** 如果没有包含,则返回原始字符串 */
      return originalString;
    }
    
    Declaration(node) {
       const value = node.value
       
        let str = value
        /** 匹配字符串带px的数组 */
        const matches = str.match(/(\d+(\.\d+)?)\s*px/g) || []

        matches.forEach(item => {
          const num = parseFloat(item)
          const replaceValue = `${((num / opt.viewPortWidth) * 100).toFixed(3)}vw`
          str = transfromValue(str, item, replaceValue)
        })

        if (!Number.isNaN(str)) node.value = str
    }
    

4.修复一个BUG

    matches.forEach(item => {
       /** 0px就没必要转换了(不然转换’box-shadow‘的时候会出问题) */
       if (item === '0px') return

       const num = parseFloat(item)
       const replaceValue = `${((num / opt.viewPortWidth) * 100).toFixed(6)}vw`
       str = transfromValue(value, item, replaceValue)
    })

七、postcss-px-to-viewport.ts完整代码

/** 编写 postcss 插件 vite内置了postCss 无需安装 */
import { Plugin } from 'postcss'
import { type Options } from './types'

const Option = {
  /** UI设计稿的宽度 给多少写多少 一般默认375 */
  viewPortWidth: 375,
}

/**
 * 转换px辅助函数
 * @param originalString 字符串来源
 * @param searchString 要被替换的值
 * @param replaceWith 替换成什么值
 * @returns
 */
const transfromValue = (
  originalString: string,
  searchString: string,
  replaceWith: string
) => {
  if (originalString.includes(searchString)) {

    /** 转义正则表达式特殊字符 */
    const escapedSearchString = searchString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    const regex = new RegExp(escapedSearchString, 'g')
    const newString = originalString.replace(regex, replaceWith)

    return newString;
  }

  /** 如果没有包含,则返回原始字符串 */
  return originalString;
}

export const PostCssPxToViewport = (options: Options = Option): Plugin => {
    const opt = Object.assign({}, Option, options)
    return {
      postcssPlugin: 'postcss-px-to-viewport',

        /** 拿到所有css节点的钩子函数 */
        Declaration(node) {
          const value = node.value

          if (!value.includes('px')) return
            
          /** 判断是否是只有一个值 */
          if (value.endsWith('px') && !value.includes(' ')) {
            /** 考虑到有小数 */
            const num = parseFloat(value)

            if (!Number.isNaN(num)) node.value = `${((num / opt.viewPortWidth) * 100).toFixed(3)}vw`
          } else {
            let str = value
            /** 匹配字符串带px的数组 */
            const matches = str.match(/(\d+(\.\d+)?)\s*px/g) || []

            matches.forEach(item => {
              /** 0px就没必要转换了(不然转换’box-shadow‘的时候会出问题) */
              if (item === '0px') return
              
              const num = parseFloat(item)
              const replaceValue = `${((num / opt.viewPortWidth) * 100).toFixed(3)}vw`
              str = transfromValue(value, item, replaceValue)
            })

            if (!Number.isNaN(str)) node.value = str
          }
        }

    }
}

八、咱们再来看看效果如何

下面的图已经看得出来不管是padding多个值还是border或者transfromY都正常的转化完成。

image.png

image.png

结语

本人主要是用RN开发APP的,Vue3没用过所以适配上面踩了一些坑,既然解决了就记录一下,如果有问题请帮忙指出哈!完结 撒花!!!