从 postcss-pxtransform 源码到 Taro 跨端单位转换方案优化

简介: 从 postcss-pxtransform 源码到 Taro 跨端单位转换方案优化

前言


目前,很多团队都选择 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


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


  • 设备分辨率:是用户比较关注的购机指标,哈哈。
  • 设备像素:是设计师关注的指标,常说的 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 像素,而不是设备像素,更不是设备分辨率。比如:

@@@@@@.webp.jpg


以 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 是必填的,但其实是没用的。


···.webp.jpg


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


??????.webp.jpg

如果 Taro 的编译配置如下:

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


????.webp.jpg


那么从源码中,可以知道调用 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 部分:


????.webp.jpg


这个方法非常简单,理解前面内容之后,看完全没有难度,本质上就是通过 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.



目录
相关文章
|
7天前
|
前端开发 JavaScript 编译器
如何利用 Babel 进行代码转换和优化?
如何利用 Babel 进行代码转换和优化?
|
27天前
|
JavaScript
如何在 Vue 项目中选择合适的模块格式
【10月更文挑战第20天】选择合适的模块格式需要综合考虑多个因素,没有一种绝对正确的选择。需要根据项目的具体情况进行权衡和分析。在实际选择过程中,要保持灵活性,根据项目的发展和变化适时调整模块格式。
20 7
|
3月前
|
前端开发 JavaScript
移动端适配方案,基于postcss
【8月更文挑战第9天】
88 2
|
4月前
|
小程序
跨端技术问题之页面或组件样式在小程序、小程序插件和小程序分包中有什么差异
跨端技术问题之页面或组件样式在小程序、小程序插件和小程序分包中有什么差异
|
4月前
|
JavaScript 前端开发
前端 JS 经典:统一 Vite 中图片转换逻辑
前端 JS 经典:统一 Vite 中图片转换逻辑
69 0
|
前端开发 JavaScript Serverless
前端工程化的前端性能的性能优化方案的渲染层面优化之CSS/JS优化
渲染是一种非常重要的前端性能优化方案,因为它可以在不同的环境中提高网页的响应速度和可接受性。
88 2
|
JavaScript 前端开发 开发者
【Vue 移动端开发】适配百分之99的屏幕方案
【Vue 移动端开发】适配百分之99的屏幕方案
253 0
|
存储 缓存 JavaScript
Vuejs设计与实现 —— 编译层面的优化
Vuejs设计与实现 —— 编译层面的优化
101 0
Vuejs设计与实现 —— 编译层面的优化
|
Rust JavaScript 前端开发
WASM 在动画引擎中的设计优化
WASM 在动画引擎中的设计优化
182 0
|
前端开发 JavaScript API
Vite 2.0 正式发布!依赖预构建,CSS 分割等众多新特性到来
Vite(法语中“快”的意思,发音为 /vit/)是一种新型 Web 开发构建工具。把它想成一个更轻、更快的预配置开发服务器 + 打包的组合工具。它利用了浏览器 native ES modules[1] 的支持,还有如 esbuild[2] 等编译工具来提供时髦酷炫的本地开发体验。
下一篇
无影云桌面