PostCSS通过px2rem插件和lib-flexible将px单位转换为rem(root em)单位实现大屏适配

目录

文档

类似的插件

amfe-flexible

lib-flexible

postcss中使用postcss-plugin-px2rem

安装postcss-plugin-px2rem

bash 复制代码
pnpm install postcss postcss-plugin-px2rem --save-dev

依赖 package.json

json 复制代码
{
    "type": "module",
    "dependencies": {
        "postcss": "^8.4.31",
        "postcss-plugin-px2rem": "^0.8.1"
    }
}

示例

main.js

js 复制代码
import { writeFileSync, readFileSync } from "fs";
import postcss from "postcss";
import pxtorem from "postcss-plugin-px2rem";

const css = readFileSync("./style.css", "utf8");

// 修改默认配置
const options = {};

const processedCss = postcss(pxtorem(options)).process(css).css;

writeFileSync("./style.rem.css", processedCss);

输入 style.css

css 复制代码
h1 {
  margin: 0 0 20px;
  font-size: 32px;
  line-height: 1.2;
  letter-spacing: 1PX; /* ignored */
}

输出 style.rem.css

css 复制代码
h1 {
  margin: 0 0 0.2rem;
  font-size: 0.32rem;
  line-height: 1.2;
  letter-spacing: 1PX;
}

默认配置

js 复制代码
{
  rootValue: 100,
  unitPrecision: 5,
  propWhiteList: [],
  propBlackList: [],
  exclude:false,
  selectorBlackList: [],
  ignoreIdentifier: false,
  replace: true,
  mediaQuery: false,
  minPixelValue: 0
}

webpack中使用postcss-plugin-px2rem

项目结构

bash 复制代码
$ tree -I node_modules
.
├── package.json
├── src
│   ├── index.js
│   └── style.css
└── webpack.config.cjs

安装依赖

bash 复制代码
pnpm install webpack webpack-cli style-loader css-loader postcss-loader mini-css-extract-plugin --save-dev

完整依赖 package.json

json 复制代码
{
  "type": "module",

  "scripts": {
    "build": "webpack"
  },

  "devDependencies": {
    "css-loader": "^6.8.1",
    "mini-css-extract-plugin": "^2.7.6",
    "postcss": "^8.4.31",
    "postcss-loader": "^7.3.3",
    "postcss-plugin-px2rem": "^0.8.1",
    "style-loader": "^3.3.3",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4"
  }
}

文件内容

webpack.config.cjs

js 复制代码
"use strict";

const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  // 生产环境
  mode: "production",

  // 打包入口
  entry: {
    index: "./src/index.js",
  },

  // 指定输出地址及打包出来的文件名
  output: {
    path: path.join(__dirname, "dist"),
    filename: "index.js",
  },

  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          MiniCssExtractPlugin.loader,
          "css-loader",
          {
            loader: "postcss-loader",
            options: {
              postcssOptions: {
                plugins: [
                  [
                    "postcss-plugin-px2rem",
                    // 配置参数
                    {
                      rootValue: 100,
                    },
                  ],
                ],
              },
            },
          },
        ],
      },
    ],
  },

  plugins: [
    // 将 CSS 提取到单独的文件中
    new MiniCssExtractPlugin(),
  ],
};

入口文件 index.js

js 复制代码
import "./style.css";

样式文件 style.css

css 复制代码
h1 {
  margin: 0 0 20px;
  font-size: 32px;
  line-height: 1.2;
  letter-spacing: 1PX; /* ignored */
}

运行打包

bash 复制代码
$ npm run build

输出结果

css 复制代码
h1 {
  margin: 0 0 0.2rem;
  font-size: 0.32rem;
  line-height: 1.2;
  letter-spacing: 1PX; /* ignored */
}

大屏适配

原理

  • 打包阶段:根据设计稿的尺寸编写css代码(px为单位),将css代码转为rem为单位的数据
  • 浏览器运行阶段:根据根节点root的字体大小,将rem为单位的尺寸还原为px单位

引入文件:lib-flexible.js

因为lib-flexible.js 原本是用来适配移动端的,所以,需要改动一些代码才能适配PC大屏,只能通过整个文件引入,不要通过npm安装,否则每次安装都需要重新修改代码

需要改动的代码如下

js 复制代码
function refreshRem() {
    var width = docEl.getBoundingClientRect().width;
    
    // 适配移动端
    // if (width / dpr > 540) {
    //   width = 540 * dpr;
    // }

    // 适配PC端
    // 最小宽度1200px
    // if (width / dpr < 1200) {
    //   width = 1200 * dpr;
    // }
    
    // 根据这个值计算postcss-plugin-px2rem的配置参数 rootValue
    // 设置px->rem的比例是:100
    // var rem = width / 10;
    var rem = width / 100;
    docEl.style.fontSize = rem + "px";
    flexible.rem = win.rem = rem;
  }

这里可以设置一个最小宽度,配置页面也设置一个最小宽度,避免屏幕过小而变形

css 复制代码
body{
	min-width: 1200px;
}

完整代码

js 复制代码
/**
 * lib-flexible
 * Version 0.3.2
 * https://www.npmjs.com/package/lib-flexible?activeTab=code
 */
(function (win, lib) {
  var doc = win.document;
  var docEl = doc.documentElement;
  var metaEl = doc.querySelector('meta[name="viewport"]');
  var flexibleEl = doc.querySelector('meta[name="flexible"]');
  var dpr = 0;
  var scale = 0;
  var tid;
  var flexible = lib.flexible || (lib.flexible = {});

  if (metaEl) {
    console.warn("将根据已有的meta标签来设置缩放比例");
    var match = metaEl
      .getAttribute("content")
      .match(/initial\-scale=([\d\.]+)/);
    if (match) {
      scale = parseFloat(match[1]);
      dpr = parseInt(1 / scale);
    }
  } else if (flexibleEl) {
    var content = flexibleEl.getAttribute("content");
    if (content) {
      var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
      var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
      if (initialDpr) {
        dpr = parseFloat(initialDpr[1]);
        scale = parseFloat((1 / dpr).toFixed(2));
      }
      if (maximumDpr) {
        dpr = parseFloat(maximumDpr[1]);
        scale = parseFloat((1 / dpr).toFixed(2));
      }
    }
  }

  if (!dpr && !scale) {
    var isAndroid = win.navigator.appVersion.match(/android/gi);
    var isIPhone = win.navigator.appVersion.match(/iphone/gi);
    var devicePixelRatio = win.devicePixelRatio;
    if (isIPhone) {
      // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
      if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
        dpr = 3;
      } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
        dpr = 2;
      } else {
        dpr = 1;
      }
    } else {
      // 其他设备下,仍旧使用1倍的方案
      dpr = 1;
    }
    scale = 1 / dpr;
  }

  docEl.setAttribute("data-dpr", dpr);
  if (!metaEl) {
    metaEl = doc.createElement("meta");
    metaEl.setAttribute("name", "viewport");
    metaEl.setAttribute(
      "content",
      "initial-scale=" +
        scale +
        ", maximum-scale=" +
        scale +
        ", minimum-scale=" +
        scale +
        ", user-scalable=no"
    );
    if (docEl.firstElementChild) {
      docEl.firstElementChild.appendChild(metaEl);
    } else {
      var wrap = doc.createElement("div");
      wrap.appendChild(metaEl);
      doc.write(wrap.innerHTML);
    }
  }

  function refreshRem() {
    var width = docEl.getBoundingClientRect().width;
    
    // 适配移动端
    // if (width / dpr > 540) {
    //   width = 540 * dpr;
    // }

    // 适配PC端
    // 最小宽度1200px
    // if (width / dpr < 1200) {
    //   width = 1200 * dpr;
    // }
    
    // 根据这个值计算postcss-plugin-px2rem的配置参数 rootValue
    // 设置px->rem的比例是:100
    // var rem = width / 10;
    var rem = width / 100;
    docEl.style.fontSize = rem + "px";
    flexible.rem = win.rem = rem;
  }

  win.addEventListener(
    "resize",
    function () {
      clearTimeout(tid);
      tid = setTimeout(refreshRem, 300);
    },
    false
  );
  win.addEventListener(
    "pageshow",
    function (e) {
      if (e.persisted) {
        clearTimeout(tid);
        tid = setTimeout(refreshRem, 300);
      }
    },
    false
  );

  if (doc.readyState === "complete") {
    doc.body.style.fontSize = 12 * dpr + "px";
  } else {
    doc.addEventListener(
      "DOMContentLoaded",
      function (e) {
        doc.body.style.fontSize = 12 * dpr + "px";
      },
      false
    );
  }

  refreshRem();

  flexible.dpr = win.dpr = dpr;
  flexible.refreshRem = refreshRem;
  flexible.rem2px = function (d) {
    var val = parseFloat(d) * this.rem;
    if (typeof d === "string" && d.match(/rem$/)) {
      val += "px";
    }
    return val;
  };
  flexible.px2rem = function (d) {
    var val = parseFloat(d) / this.rem;
    if (typeof d === "string" && d.match(/px$/)) {
      val += "rem";
    }
    return val;
  };
})(window, window["lib"] || (window["lib"] = {}));

webpack参数设置

js 复制代码
{
 loader: "postcss-loader",
  options: {
    postcssOptions: {
      plugins: [
        [
          "postcss-plugin-px2rem",
          // 配置参数
          {
            // 假设设计稿是1200px, px->rem的比例是:100
            // 1200 / 100 = 12
            rootValue: 12,
          },
        ],
      ],
    },
  },
},

比如:

设计稿尺寸为1200px

css 复制代码
.box {
  width: 300px;
  height: 100px;
  background-color: green;
  font-size: 32px;
  line-height: 1.2;
  letter-spacing: 1px; /* ignored */
}

转换结果是

css 复制代码
.box {
  width: 25rem;
  height: 8.33333rem;
  background-color: green;
  font-size: 2.66667rem;
  line-height: 1.2;
  letter-spacing: 0.08333rem; /* ignored */
}

在尺寸为1200px的屏幕宽度下,可以还原为代码中设置的尺寸

css 复制代码
width: 25rem + 12px =  300px

在尺寸为1400px的屏幕宽度下,可以还原为代码中设置的尺寸

css 复制代码
width: 25rem + 14px =  350px

这样,在不同的屏幕下,计算出来的根元素font-size就不一样,进而导致页面元素的尺寸也不一样,实现了缩放效果

需要注意的是,这个缩放是基于宽度缩放的,如果屏幕尺寸比例不一致,会导致竖向的内容会缺失,或者出现滚动

完整代码:https://github.com/mouday/webpack-lib-flexible

参考文章

  1. https://webpack.docschina.org/plugins/mini-css-extract-plugin
  2. https://webpack.docschina.org/loaders/postcss-loader/
  3. 使用lib-flexible和postcss-pxtorem解决大屏适配问题
相关推荐
如若1237 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
滚雪球~1 小时前
npm error code ETIMEDOUT
前端·npm·node.js
沙漏无语1 小时前
npm : 无法加载文件 D:\Nodejs\node_global\npm.ps1,因为在此系统上禁止运行脚本
前端·npm·node.js
supermapsupport1 小时前
iClient3D for Cesium在Vue中快速实现场景卷帘
前端·vue.js·3d·cesium·supermap
brrdg_sefg1 小时前
WEB 漏洞 - 文件包含漏洞深度解析
前端·网络·安全
胡西风_foxww1 小时前
【es6复习笔记】rest参数(7)
前端·笔记·es6·参数·rest
m0_748254881 小时前
vue+elementui实现下拉表格多选+搜索+分页+回显+全选2.0
前端·vue.js·elementui
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
m0_748234522 小时前
前端Vue3字体优化三部曲(webFont、font-spider、spa-font-spider-webpack-plugin)
前端·webpack·node.js
Web阿成2 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript