基于 rem 的前端布局方案

2020-04-29 · 11 min read

本文很简单,分享一下我们目前使用的 rem 布局方案,大体的思路和之前社区里的其他方案基本一致,但是在实际使用中发现不少问题,逐渐演变成了今天的模样。

目前这套方案大体上是比较稳定的,可能日后随着设备的更新、迭代、升级,vw 等新方案会越来越普及,布局也就不会像现在这么麻烦了。

演变过程中的问题大多比较琐碎和细节,受迫于整体开发进度比较紧张,部分兼容性问题没有记录下来,没有查清楚原因...

代码#

;(function (doc, win) {
  var docEl = doc.documentElement
  var resizeEvt = 'orientationchange' in win ? 'orientationchange' : 'resize'

  /**
   * 不管 fontsize 是多少,直接通过 clientWidth 对比
   *
   * 直接对比 fontsize 在 android webview 79+ 有问题
   * 问题表现是:
   *    有时设置 html font-size 16px,获取到是 20px,但是 rem 依然根据 16px 计算;
   *    一加 7t 上更加奇怪,具体表现待查;
   */
  var getClientWidthWithRem = function () {
    var $div = doc.createElement('div')

    $div.style.width = '3.75rem'
    $div.style.visibility = 'hidden'

    doc.head.appendChild($div)

    var width = parseFloat(win.getComputedStyle($div).width)

    doc.head.removeChild($div)

    return width
  }

  var recalc = function (event) {
    var clientWidth = docEl.clientWidth

    /**
     * 设备宽度大于 640px,fontSize 统一取上限值
     */
    if (clientWidth >= 640) {
      docEl.style.fontSize = (640 / 375) * 100 + 'px'
      return
    }

    /**
     * 传统 rem 方案
     */
    var fontSize = (clientWidth / 375) * 100
    docEl.style.fontSize = fontSize + 'px'

    /**
     * 再次校验,处理用户调整了系统字体大小的情况
     */
    var currentClientWidth = getClientWidthWithRem()

    if (Math.abs(currentClientWidth - clientWidth) > 1) {
      docEl.style.fontSize = fontSize * (clientWidth / currentClientWidth) + 'px'
    }
  }

  recalc()

  if (!doc.addEventListener) {
    return
  }

  win.addEventListener(resizeEvt, recalc, false)
  doc.addEventListener('DOMContentLoaded', recalc, false)
})(document, window)

使用前提#

  • 我们团队的视觉规范是宽 375,所以代码全部是按照 375 换算的,这是唯一前提。如果和你们的视觉规范不一致,调整计算规则即可
  • 我们给 H5 限制了最大宽度 640,一定程度上保证了页面在 PC 浏览器上的体验,这里大家自行酌情调整

使用方式#

  • 将代码压缩后插入 <head>(可以使用 terser 压缩),不建议外链,关键渲染路径优化
  • 而后,所有尺寸除以 100,加单位 rem 即可。例如一个图片在视觉稿上宽 200,代码里写 2rem 即可
  • 一些如 1px 边框的问题,直接 transform 缩放即可
  • 字体看情况处理,用 rem 和 px 都可以,主要还是看规范