美文网首页APP & program
从 postcss-pxtransform 源码到 Taro 跨

从 postcss-pxtransform 源码到 Taro 跨

作者: 越前君 | 来源:发表于2022-05-07 22:41 被阅读0次
    配图源自 Freepik

    目前,很多团队都选择 Taro 作为跨端跨框架解决方案,它可以使用 React、Vue 等语法,并支持编译为微信小程序、支付宝小程序、百度小程序以及 H5 等应用。

    一、开始

    # 全局安装 Taro
    $ yarn global add @tarojs/cli
    
    # 初始化项目
    $ taro init simple-taro
    
    # 启动/打包项目
    $ yarn dev:weapp
    $ yarn build:weapp
    

    注意,@taro/cli@tarojs/taro 版本应保持一致,若版本升级应两者同步调整,以避免两者版本不一致导致的一些编译问题。

    当我们在本地创建 Taro 项目之后,如果是 750px 的设计稿尺寸,通常会对 Taro 的编译配置调整为:

    const config = {
      designWidth: 750,
      deviceRatio: { 750: 1 }
    }
    

    除此之外,目前 Taro 还支持 640px828px 两种尺寸的设计稿,更多请看设计稿及尺寸单位

    那么,我们在编写 CSS 样式的时候,只要使用 px 单位即可,Taro 打包时会使用插件将其转换为对应平台的单位(如 rpxrem)。

    /* 编译前 */
    .avatar {
      width: 50px;
    }
     
    /* 编译为小程序 */
    .avatar {
      width: 50rpx;
    }
     
    /* 编译为 H5 */
    .avatar {
      width: 1.0667rem;
    }
    

    如果是通过 JavaScript 等书写样式,Taro 无法在编译时对其进行单位的转换,那么 Taro 提供了 Taro.pxtransform API:

    Taro.pxtransform(50) // 小程序:rpx,H5:rem
    

    如果某些场景下,不想做单位的转换,那么只要将 px 单位,书写成 PxPX 即可。

    .avatar {
      width: 50Px; /* 将会被忽略 */
    }
    

    若要忽略某个文件,则在文件顶部添加 /* postcss-pxtransform disable */ 注释即可。

    但请注意,以上忽略规则,仅仅忽略了 Taro 编译时单位转换。如果项目中使用了 Prettier 或 Stylelint 等格式化工具(或编辑器中启用了某个格式化插件)的话,在保存文件时,由于自动格式化,可能会对样式文件进行 lowercase 处理,因此需要添加对应的 ignore 处理(比如 /* prettier-ignore */ 等)。

    除了以上常用的功能之外,还提供了如下配置项:

    postcss: {
      pxtransform: {
        enable: true,
        config: {
          onePxTransform: true, // 设置 1px 是否需要被转换
          unitPrecision: 5, // rem 单位允许的小数位
          propList: ['*'], // 允许转换的属性
          selectorBlackList: [], // 黑名单里的选择器将会被忽略,不做转换处理
          replace: true, // 直接替换而不是追加一条进行覆盖
          mediaQuery: false, // 允许媒体查询里的 px 单位转换
          minPixelValue: 0 // 设置一个可被转换的最小 px 值
        }
      }
    }
    

    二、为什么还要优化呢?

    既然 Taro 已经提供了相对比较完备的解决方案,为什么还要优化呢?

    本文将会以 750px 设计稿为例。

    痛点在哪?请看前面的示例:

    /* 编译前 */
    .avatar {
      width: 50px;
    }
     
    /* 编译为小程序 */
    .avatar {
      width: 50rpx;
    }
     
    /* 编译为 H5 */
    .avatar {
      width: 1.0667rem;
    }
    

    从编译结果看,50px 转换为 H5 之后,对应大小是 1.0667rem如果我们在开发过程中需要使用 Chrome DevTools 对页面进行调试,当我们尝试对 width: 1.0667rem 进行修改,是不是很头痛?

    假设编译结果如下,换算是不是没有负担了,即使在原来基础上添加 “2px” 进行调试,是不是改为 0.52rem 就好了。

    /* 编译前 */
    .avatar {
      width: 50px;
    }
    
    /* 编译为 H5 */
    .avatar {
      width: 0.5rem;
    }
    

    但注意,小程序还是使用 Taro 默认的转换方案,下面会介绍 H5 端的实现。

    三、转换原理

    在实现以上设想之前,我们需要了解下 Taro 的实现原理(不难)。

    我想,如果开发过 H5 项目,应该使用过 postcss-pxtorem 这个插件做单位转换。顾名思义,就是 px 转换为 rem。而 Taro 的单位转换插件 postcss-pxtransform 正是基于它二次开发而来的,在原来的基础拓展了对小程序的支持,即 px 转换为 rpx

    我们知道 CSS 中的相对长度单位 rem 的参照物是根元素(<html>)的字体大小,当根元素的字体大小为 16px 时,1rem 表示 16px 的长度。

    那么,假设设计稿中某处长度为 123px 时,按照根元素字号 16px 来换算,对应就是 123 / 16 = 7.6875rem,这样的话换算负担非常大。

    试想,如果换算的基础值是 100,那么无论你是 123px,还是 345px,那么换算为 rem 只要除以 100 就好,这样的话换算负担为「零」。

    3.1 postcss-pxtorem 的使用

    在非 Taro 项目中,我通常是这样使用 postcss-pxtorem 的:

    // postcss.config.js
    module.exports = {
      // ...
      plugins: [
        require('postcss-pxtorem')({
          propList: ['*'],
          rootValue: 100,
          minPixelValue: 2
        })
      ]
    }
    

    相应 Webpack 配置就不细说了,很简单你们都懂的。在说明为什么这样设置之前,我们先看下 postcss-pxtorem 的配置项的默认值:

    {
      rootValue: 16,
      unitPrecision: 5,
      propList: ['font', 'font-size', 'line-height', 'letter-spacing'], // 这些 CSS 属性将会被转换
      selectorBlackList: [],
      replace: true,
      mediaQuery: false,
      minPixelValue: 0,
      exclude: /node_modules/i,
    }
    

    基本与前面提到的一致,这里仅介绍 rootValue 配置项,它接受一个 NumberFunction 参数,描述如下:

    Represents the root element font size or returns the root element font size based on the input parameter.

    简单来说,书写值/rootValue = 转换值,比如源码中编写的是 50pxrootValue 配置值为 16,那么转换结果为 50/16 = 3.125rem

    所以,我通常设置为 100 的原因就是:换算负担最小,等于「零」负担。这样的话 50px 换算为 0.5rem123px 换算为 1.23rem

    3.2 设备像素与 CSS 像素

    在往下之前,先了解下这些内容:

    设备 设备分辨率 设备像素 CSS 像素 设备像素比
    iPhone 5/5s 640 × 1136 640 × 1136 320 × 568 2
    iPhone 6/6s/7/8 750 × 1334 750 × 1334 375 × 667 2
    iPhone 6/6s/7/8 Plus 1080 × 1920 1242 × 2208 414 × 736 3
    iPhone X/XS 1125 × 2436 1125 × 2436 375 × 812 3
    iPhone XR 828 × 1792 828 × 1792 414 × 896 2
    iPhone 11 Pro 1125 × 2436 1125 × 2436 375 × 812 3
    iPhone XS Max/11 Pro Max 1242 × 2688 1242 × 2688 414 × 896 3
    iPhone 12 mini 1125 × 2436 1125 × 2436 375 × 812 2
    iPhone 12/12 Pro 1170 × 2532 1170 × 2532 390 × 844 3
    iPhone 12 Pro Max 1284 × 2778 1284 × 2778 428 × 926 3

    这里,推荐有两个站点 YESVIZMy Device,里面可以查看常见设备的各种参数。

    以上那么多分辨率、像素啥的,怎么区分呢:

    • 设备分辨率:是用户比较关注的购机指标,哈哈。
    • 设备像素:是设计师关注的指标,常说的 750px 设计稿尺寸,对应的 750 就是指设备像素的宽度
    • CSS 像素:是开发者需关注的指标,同时要理解设备像素与 CSS 像素的关系。
    • 设备像素比:等于“设备像素 / CSS 像素”。

    我们是不是经常看得到以下 <meta> 元素对 Viewport(视口)的声明:

    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
    
    • width=device-width:定义 Viewport 宽度,由于各浏览器默认的 Viewport 宽度可能是不同的,加之移动设备的屏幕大小寸土寸金,因此通常会将设备宽度设置为 Viewport 宽度。
    • initial-scale=1:定义设备宽度与 Viewport 之间的缩放比例。
    • minimum-scale=1:定义缩放比例的最小值。
    • maximum-scale=1:定义缩放比例的最小值。
    • user-scalable:取值 yesno,其中 no 表示用户将无法缩放当前页面。

    那么,为什么通常会这样设置呢?

    原来「设备像素比」是指在未缩放状态下,设备像素与 CSS 像素的初始比例关系。

    当网页缩放比例设为 1 时,document.documentElement.clientWidth 的返回值等于该设备横向 CSS 像素宽度。

    3.3 动态设置根元素字体大小

    接下来,介绍如何动态地设置根元素 <html> 的字体大小。

    需要知道的是,通过 JavaScript 脚本获取的某个元素的宽高等长度,对应的是 CSS 像素,而不是设备像素,更不是设备分辨率。比如:

    以 750px 设计稿为例,其课代表是 iPhone 7 等机型。

    设备 设备像素 CSS 像素 设备像素比
    iPhone 6/6s/7/8 750 × 1334 375 × 667 2

    话句话说,750px 设计稿上的 100px 对应 iPhone 7 的 CSS 像素则为 50px,所以将根元素字体大小设为 50px,此时 1rem = 50 CSS 像素 = 100 设备像素

    那么,如何适配其他设备呢,基于 iPhone 7 的 375px 横向宽度计算即可,如下:

    rootFontSize = document.documentElement.clientWidth / 375 * 50
    

    完整实现如下:

    <script>
      !(function (n, e) {
        var t = n.documentElement
        var i = 'orientationchange' in window ? 'orientationchange' : 'resize'
        var d = function () {
          var n = t.clientWidth
          if (n) {
            var e = 50 * (n / 375)
            e = e > 54 ? 54 : e
            t.style.fontSize = e + 'px'
          }
        }
        if (n.addEventListener) {
          e.addEventListener(i, d)
          n.addEventListener('DOMContentLoaded', d)
        }
      })(document, window)
    </script>
    

    需监听下 DOMContentLoadedorientationchange 事件,触发时重新设置根元素字体大小。

    四、Taro 转换原理

    前面提到 Taro 团队对 postcss-pxtorem 进行了二次开发,以适配多端的单位转换。

    其插件地址请看 postcss-pxtransform

    其配置项与 postcss-pxtorem 是类似的,最大的区别在于 rootValue 上。尽管在自述文件中说明了 rootValue 是必填的,但其实是没用的。

    从源码中看,rootValue 在不同端会有不同的换算规则。

    如果 Taro 的编译配置如下:

    const config = {
      designWidth: 750,
      deviceRatio: { 750: 1 }
    }
    

    那么从源码中,可以知道调用 rootValue() 方法,将会得到什么值。以 50px 为例:

    小程序端:

    options.rootValue = input => 1 / options.deviceRatio[designWidth(input)]
    
    // 根据配置,可知 designWidth(input) 结果为 750,
    // 因此 options.deviceRatio[designWidth(input)] 即为 1
    // 所以,小程序端转换,仅涉及单位的转换(px => rpx),数值是不变的,即转换结果为 50rpx。
    

    H5端:

    options.rootValue = input => baseFontSize * designWidth(input) / 640
    
    // 其中 baseFontSize 是源码中写死的 40
    // 其中 designWidth(input) 为 750,
    // 因此该方法返回值将会是 46.875
    // 所以 H5 端转换,50px 将会转换为 1.06666667 rem
    

    到这里,你应该就明白其转换结果为什么会是这样的了。

    /* 编译前 */
    .avatar {
      width: 50px;
    }
     
    /* 编译为小程序 */
    .avatar {
      width: 50rpx;
    }
     
    /* 编译为 H5 */
    .avatar {
      width: 1.0667rem;
    }
    

    具体的转换过程,请看源码 createPxReplace 部分:

    这个方法非常简单,理解前面内容之后,看完全没有难度,本质上就是通过 String.prototype.replace() 方法来替换字符串而已。从这里,你也理解了 onePxTransformminPixelValue 配置项的作用。

    至于匹配 px 的正则表达式如下(源码在这里):

    const pxRegex = /"[^"]+"|'[^']+'|url\([^\)]+\)|(\d*\.?\d+)px/g
    

    五、Taro H5 转换优化

    使用 Taro 初始化的项目中 index.html 是这样处理的:

    <script>
      !(function (n) {
        function e() {
          var e = n.document.documentElement,
            t = e.getBoundingClientRect().width;
          e.style.fontSize =
            t >= 640 ? "40px" : t <= 320 ? "20px" : (t / 320) * 20 + "px";
        }
        n.addEventListener("resize", function () {
          e();
        }),
          e();
      })(window);
    </script>
    

    我们将其修改为前面动态设置根元素 font-size 的方法:

    <script>
      !(function (n, e) {
        var t = n.documentElement
        var i = 'orientationchange' in window ? 'orientationchange' : 'resize'
        var d = function () {
          var n = t.clientWidth
          if (n) {
            var e = 50 * (n / 375)
            e = e > 54 ? 54 : e
            t.style.fontSize = e + 'px'
          }
        }
        if (n.addEventListener) {
          e.addEventListener(i, d)
          n.addEventListener('DOMContentLoaded', d)
        }
      })(document, window)
    </script>
    

    通过源码,我们知道 H5 中 rootValue 的计算如下,由于我们的 designWidth750,而 baseFontSize 则是写死的 40

    options.rootValue = input => baseFontSize * designWidth(input) / 640
    

    那么如果要使得 rootValue() 方法的返回值为 100,意味着需要将 designWidth 设为 1600。但是小程序端的 designWidth 仍要设为 750,因此通过 process.env.TARO_ENV 变量来控制即可,如下:

    const config = {
      designWidth: process.env.TARO_ENV === 'h5' ? 1600 : 750,
      deviceRatio: { 750: 1 },
      mini: {
        postcss: {
          pxtransform: {
            enable: true,
            config: {
              platform: 'weapp',
              minPixelValue: 2,
              onePxTransform: false
            }
          }
        }
      },
      h5: {
        postcss: {
          pxtransform: {
            enable: true,
            config: {
              platform: 'h5',
              minPixelValue: 2,
              onePxTransform: false,
            }
          }
        }
      }
    }
    

    至此,就能实现类似 postcss-pxtorem 设置 rootValue100 的效果,编译前后就如预期所想:

    /* 编译前 */
    .avatar {
      width: 50px;
    }
     
    /* 编译为小程序 */
    .avatar {
      width: 50rpx;
    }
     
    /* 编译为 H5 */
    .avatar {
      width: 0.5rem;
    }
    

    The end.

    相关文章

      网友评论

        本文标题:从 postcss-pxtransform 源码到 Taro 跨

        本文链接:https://www.haomeiwen.com/subject/wcoturtx.html