vite+vant3使用viewport响应式,兼容windicss

514 阅读1分钟

最近公司项目开发移动端,需要原生应用嵌入H5页面,目前响应式兼容方案一个是rem,另一个是viewport. 但是rem方案已经目前已经不建议使用.所以使用viewport方案替代 目前社区有个一个webpack版本的 postcss-px-to-viewport,但是有一个问题,他的正则规则会默认转化, css内书写的 数字开头的px结尾的都会转化 例如


<div class="box_2 flex-row pl-15px">

  </van-radio-group>
</div>

例如 pl-15px 这个是windcss 里边的padding-left里边的写法,举个例子默认规则会把 css转化为 pl-2.6666vw 导致windcss不生效.所以将源码修改一下并替换成vite版本


// 默认参数
let defaultsProp = {
  unitToConvert: 'px', // 要转化的单位
  viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750
  unitPrecision: 5, // 指定`px`转换为视窗单位值的小数位数
  viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
  fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,建议使用vw
  minPixelValue: 1, // 默认值1,小于或等于`1px`不转换为视窗单位
};
function toFixed(number, precision) {
  var multiplier = Math.pow(10, precision + 1),
    wholeNumber = Math.floor(number * multiplier);
  return (Math.round(wholeNumber / 10) * 10) / multiplier;
}
//
function createPxReplace(viewportSize, minPixelValue, unitPrecision, viewportUnit) {
  return function ($0, $1) {
    if (!$1) return;
    var pixels = parseFloat($1);
    if (pixels <= minPixelValue) return;
    return toFixed((pixels / viewportSize) * 100, unitPrecision) + viewportUnit;
  };
}
const templateReg = /<template>([\s\S]+)</template>/gi;
// const pxGlobalReg = /(\d+)px/gi;
const pxGlobalReg = /(?<=[^-\d])(\d+)px/gi; //修改正则表达式排除-px这种情况
// 生成插件的工厂方法
const pluginGenerator = (customOptions = defaultsProp) => {
  // 返回插件
  return {
    // 插件名称
    name: 'style-postcss-px-to-viewport',

    // transform 钩子
    async transform(code, id) {
      let _source = '';
      if (/.vue$/.test(id)) {
        let _source = '';
        if (templateReg.test(code)) {
          _source = code.match(templateReg)[0];
        }
        if (pxGlobalReg.test(_source)) {
          let $_source = _source.replace(
            pxGlobalReg,
            createPxReplace(
              customOptions.viewportWidth,
              customOptions.minPixelValue,
              customOptions.unitPrecision,
              customOptions.viewportUnit,
            ),
          );

          code = code.replace(_source, $_source);
        }
      }
      return { code };
    },
  };
};

export default pluginGenerator;

然后我们在vite.config.js中引入这个文件

image.png 注意需要放在插件当中的第一个位置,确保项目开始的时候就会使用他. 另外 cnjm-postcss-px-to-viewport这个库可以兼容 vant375设计稿的尺寸 在 项目根目录创建 postcss.config.cjs,cjs在vite中是为了兼容require方式导入

const path = require('path');

module.exports =(ctx)=>{
    return     {
        plugins: [
            require("autoprefixer"),
            // require('@minko-fe/postcss-pxtorem')({
            //     rootValue({file}) {
            //         // return  75
            //         return file.indexOf('vant') !== -1 ? 37.5 : 75;
            //     },
                // 换算基数,1rem相当于10px,值为37.5时,1rem为20px,淘宝的flex默认为1rem为10px
                // unitPrecision: 5, //允许REM单位增长到的十进制数字。
                // propWhiteList: [],  //默认值是一个空数组,这意味着禁用白名单并启用所有属性。
                // propBlackList: ['font-size', 'border'], //黑名单
                // exclude: /(node_module)/, //默认false,可以(reg)利用正则表达式排除某些文件夹的方法,例如/(node_module)/如果想把前端UI框架内的px也转换成rem,请把此属性设为默认值
                // // selectorBlackList: [], //要忽略并保留为px的选择器
                // // ignoreIdentifier: false,  //(boolean/string)忽略单个属性的方法,启用ignoreidentifier后,replace将自动设置为true。
                // // replace: true, // (布尔值)替换包含REM的规则,而不是添加回退。
                // mediaQuery: false, //(布尔值)允许在媒体查询中转换px。
                // minPixelValue: 3, //设置要替换的最小像素值(3px会被转rem)。 默认 0
            // }),
            require("cnjm-postcss-px-to-viewport")({
                viewportWidth: 750, //视窗的宽度,对应的是我们设计稿的宽度,一般是750
                customFun: ({ file }) => {
                    // 这个自定义的方法是针对处理vant组件下的设计稿为375问题
                    const designWidth = path.join(file).includes(path.join("node_modules", "vant")) ? 375 : 750;
                    return designWidth;
                },
            })
        ],

    }

    }

上次一个方案只会转化template里边的静态的px,如果我们想转化scriptjs里边写的px怎么办? 只需要替换一下正则就可以

const templateReg = /((?:<scrip|<template>)([\s\S]+)(?:</script>|</template>))/gi;

所以我们最终的插件是这样的


// 默认参数
let defaultsProp = {
  unitToConvert: 'px', // 要转化的单位
  viewportWidth: 750, // 视窗的宽度,对应的是我们设计稿的宽度,一般是750
  unitPrecision: 5, // 指定`px`转换为视窗单位值的小数位数
  viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
  fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,建议使用vw
  minPixelValue: 1, // 默认值1,小于或等于`1px`不转换为视窗单位
};
function toFixed(number, precision) {
  var multiplier = Math.pow(10, precision + 1),
    wholeNumber = Math.floor(number * multiplier);
  return (Math.round(wholeNumber / 10) * 10) / multiplier;
}
//
function createPxReplace(viewportSize, minPixelValue, unitPrecision, viewportUnit) {
  return function ($0, $1) {
    if (!$1) return;
    var pixels = parseFloat($1);
    if (pixels <= minPixelValue) return;
    return toFixed((pixels / viewportSize) * 100, unitPrecision) + viewportUnit;
  };
}
// const templateReg = /<template>([\s\S]+)</template>/gi;
// const templateReg = /(<template>([\s\S]+)</template>)/gi;
// const templateReg = /(<scrip([\s\S]+)</script>)/gi;
// const pxGlobalReg = /(\d+)px/gi;
const templateReg = /((?:<scrip|<template>)([\s\S]+)(?:</script>|</template>))/gi;
const pxGlobalReg = /(?<=[^-\d])(\d+)px/gi; //修改正则表达式排除-px这种情况
// 生成插件的工厂方法
const pluginGenerator = (customOptions = defaultsProp) => {
  // 返回插件
  return {
    // 插件名称
    name: 'style-postcss-px-to-viewport',

    // transform 钩子
    async transform(code, id) {
      let _source = '';
      if (/.vue$/.test(id)) {
        let _source = '';
        if (templateReg.test(code)) {
          _source = code.match(templateReg)[0];
        }
        if (pxGlobalReg.test(_source)) {
          let $_source = _source.replace(
            pxGlobalReg,
            createPxReplace(
              customOptions.viewportWidth,
              customOptions.minPixelValue,
              customOptions.unitPrecision,
              customOptions.viewportUnit,
            ),
          );

          code = code.replace(_source, $_source);
        }
      }
      return { code };
    },
  };
};

export default pluginGenerator;