使用postcss处理移动端各种兼容问题

306 阅读2分钟

postcss是什么?

是一个用 JavaScript 工具和插件转换 CSS 代码的工具。譬如将css属性转换成符合多个浏览器css属性;再譬如将px转换成vh和vw。常用的插件有postcss-pxtorem,Autoprefixer等。

如何开发一款CSS插件

首先我们要了解,css插件整体流程: 先将 css 源码转换为 AST,插件基于转换后 AST 的信息进行个性化处理,最后 PostCSS 再将处理后的 AST 信息转换为 css 源码,完成 css 样式转换
css--->parser--->plugin1--->plugin2--->stringifier--->new css

  • Root: 根结点,整个处理过程基本上都在围绕着 Root,Commont,AtRule,Rule 都是它的子节点。

  • Commont: css 中的注释信息,注释的内容在 comment.text 下。

  • AtRule: 带@标识的部分,name 为标识名称,params 为标识参数。nodes 为内部包含的其他子节点,可以是 Commont,AtRule,Rule,这让我们可以自定义更多的规则。

  • Declaration:每个 css 属性以及属性值就代表一个 declaration

针对这些个类型, 文档中给我们提供了找到这些类型节点的方法如下, 具体可查看文档, 地址: postcss:

  • walk
  • walkAtRules
  • walkComments
  • walkDecls
  • walkRules

通过postcss我要解决什么问题

对于ToC做移动端网页,想必最头疼的要属对各种机型的css适配问题。

  • 0.5pxborder问题
  • 不同机型字重问题,表现为字体粗细不一致
  • 不同机型的行高问题、字体大小问题在不同机型上由于内核问题导致无法居中的问题
  • 解决从蓝湖或者其他设计稿上复制的字体型号

那如何通过工程化的思想来解决很有必要。

  • 通过伪元素+transform来控制1px问题,代码如下
const { multiply } = require('./util')

/**
 * 处理小于 1px 的边框,解决 android 不支持 0.5px 边框的问题
 * @param {*} decl
 * @param {*} options.remRootValue rem 基准值
 * @param {*} options.minBorderWidth 处理的最小边框
 * @returns
 */
function convert(decl, options) {
  const arr = decl.value.split(' ')
  const value = arr[0].toLowerCase()
  const match = value.match(/([\d|.]+)/)
  const minBorderWidth = options.minBorderWidth
  if (match) {
    const borderWidthNum = parseFloat(match[0])
    const borderWidthUnit = value.replace(borderWidthNum, '').toLowerCase()
    if (
      (borderWidthUnit === 'rem' && borderWidthNum < minBorderWidth / options.remRootValue) ||
      (borderWidthUnit === 'px' && borderWidthNum < minBorderWidth)
    ) {
      const parent = decl.parent
      let borderRadiusDecl = null
      let hasPosition = false
      for (const node of parent.nodes) {
        if (node.type === 'decl') {
          if (node.prop === 'border-radius') {
            borderRadiusDecl = node
          } else if (node.prop === 'position') {
            hasPosition = true
          }
        }
      }
      if (!hasPosition) {
        parent.append(decl.clone({ prop: 'position', value: 'relative' }))
      }
      arr[0] = '1px'
      const borderDecl = decl.clone({ value: arr.join(' ') })
      const cloneRule = parent.clone()
      cloneRule.selector += ':after'
      cloneRule.removeAll()
      // 追加伪元素
      cloneRule.append(decl.clone({ prop: 'content', value: '""' }))
      cloneRule.append(decl.clone({ prop: 'position', value: 'absolute' }))
      cloneRule.append(decl.clone({ prop: 'top', value: '0' }))
      cloneRule.append(decl.clone({ prop: 'left', value: '0' }))
      cloneRule.append(decl.clone({ prop: 'width', value: '200%' }))
      cloneRule.append(decl.clone({ prop: 'height', value: '200%' }))
      cloneRule.append(decl.clone({ prop: 'transform', value: 'scale(0.5)' }))
      cloneRule.append(decl.clone({ prop: 'transform-origin', value: 'left top' }))
      cloneRule.append(borderDecl)
      if (borderRadiusDecl) {
        cloneRule.append(borderRadiusDecl.clone({ value: multiply(borderRadiusDecl.value, 2) }))
      }
      cloneRule.raws.before = parent.raws.before || '\n'
      decl.value = value + ' solid transparent'
      parent.after(cloneRule)
    }
  }
}

module.exports = {
  convert
}

  • 解决字体样式问题
/**
 * 处理字体样式,调整从蓝湖或 mastergo 上拷贝的字体样式,避免和全局样式冲突
 * @param {*} decl
 * @param {*} options.baseFontFamily 基础字体样式
 * @returns
 */
function convert(decl, options) {
  const ff = decl.value.replace(/'|"/gi, '').replace(', ', ',')
  const normalFf = `PingFangSC-Regular`
  const systemFf = 'system-ui'
  if (ff.indexOf(normalFf) >= 0) {
    decl.remove()
  } else if (ff.indexOf(systemFf) < 0) {
    const parent = decl.parent
    if (parent.type === 'rule') {
      // 如果是自定义字体,在后面追加全局字体样式,避免系统不支持自定义字体时显示异常
      const clonedDecl = decl.clone({
        value: ff.replace('PingFang SC', ` "PingFang SC"`) + ', ' + options.baseFontFamily
      })
      const cloneRule = parent.clone()
      cloneRule.removeAll()
      cloneRule.append(clonedDecl)
      cloneRule.raws.before = parent.raws.before || '\n'
      parent.after(cloneRule)
      decl.remove()
    }
  }
}

module.exports = {
  convert
}

其他的接种情况同理进行操作即可。
使用方法:

yarn add postcss-mobile || npm install postcss-mobile

地址传送门

针对多文件的postcss插件 很有用,手动狗头