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


    文档

    类似的插件

    amfe-flexible
    lib-flexible

    postcss中使用postcss-plugin-px2rem

    安装postcss-plugin-px2rem

    pnpm install postcss postcss-plugin-px2rem --save-dev
    
    • 1

    依赖 package.json

    {
        "type": "module",
        "dependencies": {
            "postcss": "^8.4.31",
            "postcss-plugin-px2rem": "^0.8.1"
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    示例

    main.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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    输入 style.css

    h1 {
      margin: 0 0 20px;
      font-size: 32px;
      line-height: 1.2;
      letter-spacing: 1PX; /* ignored */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输出 style.rem.css

    h1 {
      margin: 0 0 0.2rem;
      font-size: 0.32rem;
      line-height: 1.2;
      letter-spacing: 1PX;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    默认配置

    {
      rootValue: 100,
      unitPrecision: 5,
      propWhiteList: [],
      propBlackList: [],
      exclude:false,
      selectorBlackList: [],
      ignoreIdentifier: false,
      replace: true,
      mediaQuery: false,
      minPixelValue: 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    webpack中使用postcss-plugin-px2rem

    项目结构

    $ tree -I node_modules
    .
    ├── package.json
    ├── src
    │   ├── index.js
    │   └── style.css
    └── webpack.config.cjs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    安装依赖

    pnpm install webpack webpack-cli style-loader css-loader postcss-loader mini-css-extract-plugin --save-dev
    
    • 1

    完整依赖 package.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"
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    文件内容

    webpack.config.cjs

    "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(),
      ],
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    入口文件 index.js

    import "./style.css";
    
    
    • 1
    • 2

    样式文件 style.css

    h1 {
      margin: 0 0 20px;
      font-size: 32px;
      line-height: 1.2;
      letter-spacing: 1PX; /* ignored */
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行打包

    $ npm run build
    
    • 1

    输出结果

    h1 {
      margin: 0 0 0.2rem;
      font-size: 0.32rem;
      line-height: 1.2;
      letter-spacing: 1PX; /* ignored */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    大屏适配

    原理

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

    引入文件:lib-flexible.js

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

    需要改动的代码如下

    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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

    body{
    	min-width: 1200px;
    }
    
    • 1
    • 2
    • 3

    完整代码

    /**
     * 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"] = {}));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156

    webpack参数设置

    {
     loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            [
              "postcss-plugin-px2rem",
              // 配置参数
              {
                // 假设设计稿是1200px, px->rem的比例是:100
                // 1200 / 100 = 12
                rootValue: 12,
              },
            ],
          ],
        },
      },
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    比如:

    设计稿尺寸为1200px

    .box {
      width: 300px;
      height: 100px;
      background-color: green;
      font-size: 32px;
      line-height: 1.2;
      letter-spacing: 1px; /* ignored */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    转换结果是

    .box {
      width: 25rem;
      height: 8.33333rem;
      background-color: green;
      font-size: 2.66667rem;
      line-height: 1.2;
      letter-spacing: 0.08333rem; /* ignored */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    width: 25rem + 12px =  300px
    
    • 1

    在这里插入图片描述

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

    width: 25rem + 14px =  350px
    
    • 1

    在这里插入图片描述
    这样,在不同的屏幕下,计算出来的根元素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解决大屏适配问题
  • 相关阅读:
    LibreOffice编辑excel文档如何在单元格中输入手动换行符
    基于单片机微波炉加热箱系统设计
    vue2 element手术麻醉信息系统源码,手术预约、手术安排、排班查询、手术麻醉监测、麻醉记录单
    1972:【15NOIP普及组】推销员
    jwt not active
    Apisix网关-使用Grafana可视化Apisix的Prometheus数据
    Vue+elementUI 导出word打印
    我与足球以及世界杯的过往
    CSS基础选择器总结
    有关动态规划的相关优化思想
  • 原文地址:https://blog.csdn.net/mouday/article/details/134079601