Skip to content

移动端适配之vw

移动端兼容解决方案 —— vw

1vw 即值为视口宽度的 1%

meta可配置视窗大小和初始缩放比例,目前仅作用于移动设备;见meta标签作用

安装依赖

  • npm install postcss-px-to-viewport-update -D

最新版postcss-px-to-viewport未发布到npm库,include配置不生效,见issue

TIP

如果只需要转换某一个文件夹下的css为vw,只需要配置include属性就ok

如果include&exclude都不配置,则转换所有文件为vw

⚠需要注意windows/linux下path路径中的反斜杠符号

nuxt.config.js 配置(nuxt2,nuxt3暂未测试)

WARN⚠ Please use build.postcss in your nuxt.config.js instead of an external config file. Support for such files will be removed in Nuxt 3 as they remove all defaults set by Nuxt and can cause severe problems with features like alias resolving inside your CSS.

nuxt2项目配置

nuxt2项目中postcss.config.js配置在项目根目录未生效,需配置在nuxt.config.js中build属性中

js
  build: {
    // ...
    postcss: {
      plugins: {
        'postcss-px-to-viewport-update': {
          unitToConvert: 'px', // 需要转换的单位,默认为"px"
          viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度
          viewportHeight: 667, // 视窗的高度,根据375设备的宽度来指定,一般指定667,也可以不配置
          unitPrecision: 13, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
          propList: ['*'], // 能转化为vw的属性列表
          viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
          fontViewportUnit: 'vw', // 字体使用的视口单位
          selectorBlackList: ['.ignore-', 'van'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加
          minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
          mediaQuery: false, // 允许在媒体查询中转换`px`
          replace: true, // 是否直接更换属性值,而不添加备用属性
          include: [/pages\/?webapp/], // 兼容windows,反斜杠需考虑不匹配
          <!--    👆正则转义问题:https://chaoszhu.com/front-end/regexp/%E5%8C%B9%E9%85%8D%E5%AD%97%E7%AC%A6.html#%E8%BD%AC%E4%B9%89       -->
          landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
          landscapeUnit: 'vw', // 横屏时使用的单位
          landscapeWidth: 1440 // 横屏时使用的视口宽度
        }
      }
    }
  }

vue-cli项目配置

js
// 安装依赖后在根目录新增postcss.config.js即可
module.exports = {
  plugins: {
    'postcss-px-to-viewport-update': {
      unitToConvert: 'px', // 需要转换的单位,默认为"px"
      viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度
      viewportHeight: 667, // 视窗的高度,根据375设备的宽度来指定,一般指定667,也可以不配置
      unitPrecision: 13, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
      propList: ['*'], // 能转化为vw的属性列表
      viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vw
      fontViewportUnit: 'vw', // 字体使用的视口单位
      selectorBlackList: ['.ignore-', 'van'], // 指定不转换为视窗单位的类,可以自定义,可以无限添加
      minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
      mediaQuery: false, // 允许在媒体查询中转换`px`
      replace: true, // 是否直接更换属性值,而不添加备用属性
      include: /home.vue/, // 单个文件,或者配置成多个:[/home.vue/, /detail.vue/]
      landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
      landscapeUnit: 'vw', // 横屏时使用的单位
      landscapeWidth: 1440 // 横屏时使用的视口宽度
    }
  }
}

内联样式px转换为vw

TIP

postcss-px-to-viewport只会转换SFC(单文件组件)中的style中的px单位,内联(动态)样式需要自己计算.

计算方法

现设计稿为375*812,即375px=100vw, 则内联样式长度单位px需除以3.75转换为vw单位

假如设计稿一元素width为37.5px,则计算:{'width: 37.5/375*100 + 'vw'} , 即width:10vw;

转换原理

默认值

js
// 默认值
var defaults = {
  unitToConvert: 'px',
  viewportWidth: 320,
  viewportHeight: 568, // not now used; TODO: need for different units and math for different properties
  unitPrecision: 5,
  viewportUnit: 'vw',
  fontViewportUnit: 'vw',  // vmin is more suitable.
  selectorBlackList: [],
  propList: ['*'],
  minPixelValue: 1,
  mediaQuery: false,
  replace: true,
  landscape: false,
  landscapeUnit: 'vw',
  landscapeWidth: 568
}

include&exclude处理

js
// ...
checkRegExpOrArray(opts, 'exclude'); // 检查exclude&include参数是否符合合规
checkRegExpOrArray(opts, 'include');
// 使用postcss提供的api walkRules遍历文件
css.walkRules(function (rule) {
  // 【在windows目录中path分隔为反斜杠,在传入include/exclude参数需注意】
  var file = rule.source && rule.source.input.file; // 文件路径, 例如xxx.vue xxx.css
  // 处理include
  if (opts.include && file) {
    if (Object.prototype.toString.call(opts.include) === '[object RegExp]') {
      // (不符合)include正则条件的直接略过,所以一般情况只需传入include参数即可
      if (!opts.include.test(file)) return;
    } else if (Object.prototype.toString.call(opts.include) === '[object Array]') {
      var flag = false;
      // include为数组时,遍历每一条正则(有一条符合即可往下走)
      for (var i = 0; i < opts.include.length; i++) {
        if (opts.include[i].test(file)) {
          console.log('已转换vw', file)
          flag = true;
          break;
        }
      }
      if (!flag) return;
    }
  }
  // 处理exclude
  if (opts.exclude && file) {
    if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') {
      // (符合)exclude正则条件的直接略过,所以一般情况只需传入include参数即可
      if (opts.exclude.test(file)) return;
    } else if (Object.prototype.toString.call(opts.exclude) === '[object Array]') {
      for (var i = 0; i < opts.exclude.length; i++) {
        if (opts.exclude[i].test(file)) return;
      }
    }
  }
  // 黑名单前缀的class忽略
  if (blacklistedSelector(opts.selectorBlackList, rule.selector)) return;
  // ...
  rule.walkDecls(function(decl, i) {
    // ...
    var unit = opts.landscapeUnit; // 需要转换的单位 默认:px
    var size = opts.viewportWidth; // 设计稿宽度
    // ...
    // 计算value,并加上vw,赋值给css属性,完成替换
    var value = decl.value.replace(pxRegex, createPxReplace(opts, unit, size));
    if (opts.replace) {
      decl.value = value;
    } else {
      decl.parent.insertAfter(i, decl.clone({ value: value }));
    }
  });
}

计算结果函数:createPxReplace

js
function createPxReplace(opts, viewportUnit, viewportSize) {
  return function (m, $1) {
    if (!$1) return m;
    var pixels = parseFloat($1);
    // px小于最小值(默认1px)的设定时,不进行转换
    if (pixels <= opts.minPixelValue) return m;
    // 设计稿某元素宽度像素pixels / 设计稿总宽度大小(例如375) * 100
    var parsedVal = toFixed((pixels / viewportSize * 100), opts.unitPrecision);
    return parsedVal === 0 ? '0' : parsedVal + viewportUnit;
  };
}