vw布局项目转换rem 设置最大宽 宽屏两边留白

vw布局项目转换rem 设置最大宽 宽屏两边留白


css 布局 响应式 前端

问题前提如下:

目前有个移动端的项目,页面使用的 vw 布局,同时使用了 px-to-vw 插件,项目中 css 维护的不规范,有直接写 vw 单位的,也有写 px 单位的,同时还写了不少内联样式。现在该项目需要在大屏下展示(企业微信 pc 端),最终定下方案是在大屏下两边留白。我就给整个 html 设置了最大宽,为了方便就把所有页面的 vw 单位替换了 rem,然后把根节点的 font-size 设置成了百分之一页面宽度(不然 vw 和 rem 还要换算,百分之一就只需要改单位),这个时候 375px 的屏幕根节点的 font-size 就是 3.75px。

同时还把插件从 px-to-vw 替换为了 px-to-rem。

此时在部分浏览器(比如 QQ 浏览器,企业微信 pc 版打开)会因为最小支持字体 12px,比如设置了 80rem 宽度的 div 就会不是 80%的屏宽,会很大且超出设置的 html 最大宽。

首先是把 1rem=百分之一屏幕宽度,修改为 1rem=十分之一屏幕宽度,这样基本就不会出现字体太小导致浏览器不支持的情况了。

(function () {
  setRem();
  function setRem() {
    let htmlWidth =
      document.documentElement.clientWidth || document.body.clientWidth;
    //得到html的Dom元素
    let htmlDom = document.getElementsByTagName("html")[0];
    //设置根元素字体大小
    htmlDom.style.fontSize = htmlWidth / 10 + "px";
  }
  /* 监听窗口大小发生改变时*/
  window.addEventListener("resize", setRem, false);
})();

此时的问题是修改为 rem 的样式,是按照百分之一的屏宽计算的,现在改了 1rem 对应的大小后,这些样式就会有问题,总不能全局一个个找,然后手动除以 10 吧。然后去找了下 postcss 插件,发现 postcss-relaxed-unit 这个比较符合需求,于是加上一段配置,postcss 的配置完整如下:

module.exports = {
  plugins: {
    autoprefixer: {},
    //把rem单位的样式 值都除以10
    "postcss-relaxed-unit": {
      rules: { rem: "div(10).unit(rem)" },
    },
    "postcss-pxtorem": {
      rootValue: 37.5,
      propList: ["*"],
      selectorBlackList: ["html", "body"], //忽略的选择器
      unitPrecision: 5,
      replace: true,
      mediaQuery: false,
      minPixelValue: 2,
    },
  },
};

这时候已经完成了大部分的工作,但是 postcss 只能改掉 style 标签内维护的样式,而并不会修改内联样式,如果项目中只有少量地方这么写,可以一个个手动替换,但情况是有很多页面这么写,所以还得想办法处理。

最后考虑自己写一个 webpack 的 loader 来实现,在 webpack 的配置中加入此 loader 就可以对内联样式中的 rem 值进行替换,具体代码如下:

//由于postcss不支持转化内联样式 ,所以增加一个loader将内联样式内的 单位为rem的样式 值都除以10
//动态样式 如 :style = {width: max + 'rem'} 这种就只能手动改了, 目前只发现chart图的两处地方是这么写的
const template = /<template>([\s\S]+)<\/template>/gi;
const ZPXRegExp = /(([1-9]\d*\.?\d*)|(0\.\d*[1-9]))rem/;

module.exports = function (source) {
  let _source = "";
  // 如果当前的source里面存在template
  if (template.test(source)) {
    // 匹配template部分
    _source = source.match(template)[0];
  }
  // 匹配出template里面的rem
  let pxGlobalRegExp = new RegExp(ZPXRegExp.source, "ig");
  if (pxGlobalRegExp.test(_source)) {
    // rem转为 原值的1/10,核心部分
    let $_source = _source.replace(pxGlobalRegExp, createPxReplace());
    // 转换之后替换回source中,返回函数值
    return source.replace(template, $_source);
  } else {
    //没有就不转,直接返回
    return source;
  }
};

function createPxReplace() {
  ``;
  // $0, $1 他们是replace第二个参数提供的
  return function ($0, $1) {
    if (!$1) return;
    var pixels = parseFloat($1);
    if (isNaN(Number(pixels))) return;
    return toFixed(pixels / 10, 5) + "rem";
  };
}
function toFixed(number, precision) {
  var multiplier = Math.pow(10, precision + 1),
    wholeNumber = Math.floor(number * multiplier);
  return (Math.round(wholeNumber / 10) * 10) / multiplier;
}

这波改完基本就结束了,除了极个别使用动态样式的,如 :style = {width: max + ‘rem’} 这种,需要手动处理下,其他就没什么问题了。

© 2025 Niko Xie