flexible.js 响应式布局实战与移动端适配技巧

技术振艳 移动 阅读 1,212
赞 32 收藏
二维码
手机扫码查看
反馈

为什么我又翻出 flexible.js 这个老古董?

最近接手一个老项目,发现它还在用 flexible.js 做移动端适配。说实话,我一开始是想直接干掉它,换成现代方案。但一查兼容性要求——得支持 Android 4.4 和 iOS 8,行吧,那只能在“复古方案”里挑个顺手的了。于是我就把几个主流的 flexible 方案又拉出来遛了一圈,今天就聊聊我的实战感受。

flexible.js 响应式布局实战与移动端适配技巧

谁更灵活?谁更省事?

目前市面上常见的 flexible 方案其实就三种:阿里系的 lib-flexible(也就是大家常说的 flexible.js)、手淘团队后来推的 viewport + rem 简化版,以及现在更多人用的 vw/vh 纯 CSS 方案。但别被名字骗了,vw/vh 在低版本安卓上坑多到能种菜,所以本文主要对比前两个。

先说结论:如果项目必须支持老旧机型,我宁可多写几行 JS,也不碰纯 CSS 的 vw。不是 vw 不好,是它在某些低端机上根本对不上标尺,调试到崩溃。

核心代码就这几行

先看最经典的 lib-flexible 用法。它的核心思想是动态设置 <meta name="viewport">htmlfont-size,然后你用 rem 写样式。

<!-- HTML 头部 -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
// flexible.js(简化版,实际项目建议用官方 npm 包)
(function() {
  var dpr = window.devicePixelRatio || 1;
  var scale = 1 / dpr;
  var metaEl = document.querySelector('meta[name="viewport"]');
  if (!metaEl) {
    metaEl = document.createElement('meta');
    metaEl.setAttribute('name', 'viewport');
    document.head.appendChild(metaEl);
  }
  metaEl.setAttribute('content', width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no);
  document.documentElement.style.fontSize = (document.documentElement.clientWidth / 10) + 'px'; // 假设设计稿是 375px,这里除以 37.5 更常见,但为演示简化
})();

然后你的 CSS 就这么写:

.box {
  width: 2rem; /* 实际宽度 = 2 * (屏幕宽度 / 10) */
}

再看手淘后来推的“轻量版”方案,其实逻辑差不多,但去掉了 dpr 相关处理,直接按屏幕宽度算 font-size,viewport 也固定死。代码更短,但牺牲了高清屏的优化:

(function() {
  document.documentElement.style.fontSize = (document.documentElement.clientWidth / 375) * 16 + 'px';
})();

注意:这里假设设计稿是 375px 宽,16px 是基准字体。你也可以改成 100px 基准,让数字更好算。

踩坑提醒:这三点一定注意

我在这两个方案上都踩过坑,而且不止一次。

  • 动态内容插入后没触发重计算:比如你用 Vue 或 React 动态加载页面,flexible.js 只在页面加载时跑一次。如果之后屏幕尺寸变了(比如横竖屏切换),字体大小不会自动更新。解决办法很简单:加个 resize 监听。
window.addEventListener('resize', function() {
  document.documentElement.style.fontSize = (document.documentElement.clientWidth / 375) * 16 + 'px';
});

但注意别忘防抖,不然低端机卡成 PPT。

  • iOS 10+ 的 viewport 缩放问题:某些版本的 Safari 在设置了 maximum-scale=1 后,双击缩放还是会触发,导致布局错乱。后来我们干脆把 user-scalable=no 去掉,靠 CSS 禁止缩放(touch-action: manipulation),反而更稳。
  • 1px 边框在高清屏上还是粗:很多人以为用了 flexible.js 就能完美解决 1px 问题,其实不行。你得配合 transform: scale(0.5) 或者用伪元素 + background-image。这个和 flexible 本身无关,但新手常误以为是方案缺陷。

性能对比:差距比我想象的大

别小看那几行 JS。在千元机上,lib-flexible 因为要处理 dpr 和 viewport,启动时会有轻微卡顿。而轻量版因为逻辑简单,几乎无感。我自己测过,前者首屏 JS 执行时间约 8ms,后者 2ms——在低端机上,这 6ms 差距可能就是“流畅”和“卡一下”的区别。

另外,lib-flexible 会强制锁定 viewport,导致某些需要用户缩放的场景(比如图片详情页)无法实现。而轻量版只改 font-size,viewport 保持默认,灵活性更高。

我的选型逻辑

现在我基本不用 lib-flexible 了。除非项目明确要求支持 iPhone 5s 以下的设备(真的有这种需求!),否则我一律选轻量版。

为什么?因为:

  • 现代设计稿基本都是 375px 或 750px,直接按比例换算 rem 足够准
  • dpr 优化在 2024 年意义不大——现在连百元机都是 2x 屏了,强行分 1x/2x/3x 反而增加复杂度
  • 轻量版代码少,容易自己维护,还能加个 localStorage 缓存 fontSize 避免重复计算

举个我现在的标准做法:

(function() {
  const designWidth = 375;
  const rootFontSize = 16;
  const calcRem = () => {
    const clientWidth = document.documentElement.clientWidth;
    const fontSize = (clientWidth / designWidth) * rootFontSize;
    document.documentElement.style.fontSize = fontSize + 'px';
  };
  calcRem();
  let tid;
  window.addEventListener('resize', () => {
    if (tid) clearTimeout(tid);
    tid = setTimeout(calcRem, 100);
  });
})();

这段代码我复制粘贴用了三年,没出过问题。改设计稿宽度?改 designWidth 就行。想换基准字体?改 rootFontSize。简单、可控、没依赖。

最后说句实在话

Flexible 本质是“用 JS 模拟 vw”,在 vw 不可用的时代是救命稻草。但现在,如果项目能放弃 Android 4.x 和 iOS 8,直接上 vw/vh 是最爽的。一行 CSS 搞定,不用 JS,不用 rem,F12 调试都清爽。

但现实是,很多政企、金融类项目还在用老设备。这时候,别纠结“哪个更先进”,选那个你最熟、最能快速修 bug 的就行。对我而言,轻量版 flexible 足够了——它不完美,但够用,而且我闭着眼都能写出它的兼容补丁。

以上是我踩坑后的总结,希望对你有帮助。如果你有更好的 hack 或者遇到我没提到的奇葩机型问题,欢迎评论区交流。毕竟,前端适配这事,永远没有终点,只有下一个坑。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论