3. rem适配方案:从原理到实战

说到移动端适配,rem方案绝对是个绕不开的话题。我记得刚入行那会儿,还在用px写死尺寸,结果换个手机就崩得不成样子。后来接触了rem,才算是真正找到了移动端适配的「钥匙」。

今天我就带大家把rem方案彻底搞明白。从原理到实战,咱们一步步来。

3.1 rem单位原理:它到底是个啥?

rem,全称是「root em」。说白了,它就是相对于根元素(html)的字体大小来计算的。

举个例子:

/* 如果 html 的 font-size 是 16px */
html { font-size: 16px; }

/* 那么 1rem = 16px */
.box { width: 10rem; }  /* 实际宽度 = 160px */

嗯,就这么简单。但为什么它能解决适配问题呢?

你想想看:如果我们让html的字体大小随着屏幕宽度变化,那所有用rem写的尺寸不就跟着一起变了吗?

这就是rem适配的核心思想——等比缩放

核心要点:rem不是固定值,它依赖于根字体大小。根字体变了,所有rem单位的值都会跟着变。

3.2 根字体大小设置:怎么定这个基准?

我个人习惯用设计稿宽度来反推。比如设计稿是750px宽,那我们就希望在这个宽度下,1rem等于某个方便计算的数值。

常见的做法有两种:

方案 设计稿宽度 根字体大小 换算关系
方案一 750px 75px 1rem = 75px
方案二 750px 37.5px 1rem = 37.5px

方案一的好处是:设计稿上100px,直接写1.333rem(100/75),计算简单。但有个坑——很多浏览器对根字体大小有限制,最小不能低于12px,最大不能超过某个值。

方案二更稳妥,但计算时得除以37.5,稍微麻烦点。

我的建议:新手先用方案二,配合postcss-pxtorem这类工具,写px自动转rem,省心。

3.3 动态计算rem:让适配活起来

根字体大小不能写死,得根据屏幕宽度动态算。怎么做?

核心公式就一句话:

根字体大小 = 屏幕宽度 / 设计稿宽度 × 基准值

举个例子,设计稿750px,基准值75px:

// 屏幕宽度 375px(iPhone 6/7/8)
根字体大小 = 375 / 750 × 75 = 37.5px

// 屏幕宽度 414px(iPhone 6/7/8 Plus)
根字体大小 = 414 / 750 × 75 = 41.4px

代码实现也很简单:

// 动态设置根字体大小
(function() {
  var docEl = document.documentElement;
  var designWidth = 750;  // 设计稿宽度
  var baseFontSize = 75;  // 基准字体大小

  function setRem() {
    var clientWidth = docEl.clientWidth;
    if (!clientWidth) return;

    // 限制最大宽度,防止在PC上过大
    if (clientWidth > 750) {
      clientWidth = 750;
    }

    docEl.style.fontSize = (clientWidth / designWidth) * baseFontSize + 'px';
  }

  setRem();
  window.addEventListener('resize', setRem);
})();

这段代码我用了好多年,基本没出过问题。但要注意一点——一定要在页面加载前执行,不然会出现闪屏。

避坑指南:我曾经把这段代码放在window.onload里,结果页面先渲染了默认字体大小,然后才调整,导致所有元素「跳」了一下。后来改成内联脚本放在head里,问题就解决了。

3.4 lib-flexible库的使用:偷懒的正确姿势

说实话,上面那段代码虽然能用,但还不够完善。比如横竖屏切换、dpr适配等问题,自己写容易漏。

这时候就该lib-flexible出场了。它是淘宝前端团队出品的一个库,专门解决rem适配问题。

使用方式极其简单:

<!-- 引入 lib-flexible -->
<script src="https://cdn.jsdelivr.net/npm/amfe-flexible@2.2.1/index.min.js"></script>

引入之后,它会自动做三件事:

  • 动态设置根字体大小——根据屏幕宽度自动计算
  • 处理dpr——给html加上data-dpr属性,方便做高清适配
  • 监听窗口变化——横竖屏切换时自动重新计算

配合px转rem的工具,写起来特别爽:

// 以750px设计稿为例
// 直接写设计稿上的px值
.box {
  width: 750px;  /* 自动转成 10rem */
  height: 100px; /* 自动转成 1.333rem */
  font-size: 28px; /* 自动转成 0.373rem */
}

我用的是postcss-pxtorem,配置也很简单:

// postcss.config.js
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 75,  // 对应lib-flexible的基准值
      propList: ['*'], // 所有属性都转换
      selectorBlackList: ['.norem'] // 排除某些类
    }
  }
}

小技巧:如果某个属性不想转rem,可以加注释 /* no */。比如:border: 1px /* no */;,它就会保持px不变。

3.5 实战中的几个坑

做rem适配这么多年,我踩过的坑还真不少。挑几个典型的说说:

  1. 字体大小别用rem——字体用rem会导致在小屏上字太小,大屏上字太大。我一般用px配合媒体查询,或者用vw。
  2. 1px边框问题——rem缩放后,1px可能会变成0.5px或2px。解决方案是用伪元素+transform缩放,或者用0.5px的边框。
  3. 第三方组件库冲突——有些UI库自己带了rem适配,和lib-flexible一起用会打架。解决办法是统一用一个方案。

我曾经踩过的坑:有次项目用了vant组件库,它默认是基于375px设计稿的。而我用了750px的lib-flexible,结果所有组件都大了一倍。后来发现vant支持自定义rootValue,改成75就好了。

3.6 总结一下

rem适配方案,说白了就是三步:

  • 定基准——确定设计稿宽度和根字体大小
  • 动态算——根据屏幕宽度实时计算根字体
  • 写rem——开发时用rem单位,或者用工具自动转换

lib-flexible帮我们搞定了第二步,剩下的就是写代码了。我个人建议新手直接上lib-flexible+postcss-pxtorem,等熟悉了原理再考虑自己实现。

下一章我会讲vw/vh方案,到时候咱们对比一下,看看哪个更适合你的项目。


公众号:蓝海资料掘金营,微信deep3321