Flexible.js可伸缩布局实现原理与注意事项

作者:工具松鼠

时间:2026-10-23

浏览:361

本文详解Flexible.js实现移动端自适应布局的核心原理,通过动态调整html字体大小结合rem单位实现一稿多端。文章深入解析了1rem与视觉稿宽度的换算关系,提供了Sass混合宏处理不同DPR下字体的方案,并指出了CSS Sprite模糊、字体单位选择等实际开发中的关键注意事项。

文章目录

Flexible.js简介与核心原理

Flexible.js是由阿里团队开源的一个JavaScript库,旨在解决移动端界面在不同设备上的兼容与自适应问题,实现“一稿搞定”所有设备的目标。

其核心实现机制如下:

该方案通过JavaScript动态调整HTML根元素的字体大小,而页面设计稿则统一使用rem单位进行制作。关键实现代码如下:


;(function(win, lib) {
  var doc = win.document;
  var docEl = doc.documentElement;
  var metaEl = doc.querySelector('meta[name="viewport"]');
  var flexibleEl = doc.querySelector('meta[name="flexible"]');
  var dpr = 0;
  var scale = 0;
  var tid;
  var flexible = lib.flexible || (lib.flexible = {});
   
  if (metaEl) {
    console.warn('将根据已有的meta标签来设置缩放比例');
    var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
    if (match) {
      scale = parseFloat(match[1]);
      dpr = parseInt(1 / scale);
    }
  } else if (flexibleEl) {
    var content = flexibleEl.getAttribute('content');
    if (content) {
      var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
      var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
      if (initialDpr) {
        dpr = parseFloat(initialDpr[1]);
        scale = parseFloat((1 / dpr).toFixed(2));  
      }
      if (maximumDpr) {
        dpr = parseFloat(maximumDpr[1]);
        scale = parseFloat((1 / dpr).toFixed(2));  
      }
    }
  }
 
  if (!dpr && !scale) {
    var isAndroid = win.navigator.appVersion.match(/android/gi);
    var isIPhone = win.navigator.appVersion.match(/iphone/gi);
    var devicePixelRatio = win.devicePixelRatio;
    if (isIPhone) {
      // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
      if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {        
        dpr = 3;
      } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
        dpr = 2;
      } else {
        dpr = 1;
      }
    } else {
      // 其他设备下,仍旧使用1倍的方案
      dpr = 1;
    }
    scale = 1 / dpr;
  }
 
  docEl.setAttribute('data-dpr', dpr);
  if (!metaEl) {
    metaEl = doc.createElement('meta');
    metaEl.setAttribute('name', 'viewport');
    metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
    if (docEl.firstElementChild) {
      docEl.firstElementChild.appendChild(metaEl);
    } else {
      var wrap = doc.createElement('div');
      wrap.appendChild(metaEl);
      doc.write(wrap.innerHTML);
    }
  }
 
  function refreshRem(){
    var width = docEl.getBoundingClientRect().width;
    if (width / dpr > 540) {
      width = 540 * dpr;
    }
    var rem = width / 10;
    docEl.style.fontSize = rem + 'px';
    flexible.rem = win.rem = rem;
  }
 
  win.addEventListener('resize', function() {
    clearTimeout(tid);
    tid = setTimeout(refreshRem, 300);
  }, false);
  win.addEventListener('pageshow', function(e) {
    if (e.persisted) {
      clearTimeout(tid);
      tid = setTimeout(refreshRem, 300);
    }
  }, false);
 
  if (doc.readyState === 'complete') {
    doc.body.style.fontSize = 12 * dpr + 'px';
  } else {
    doc.addEventListener('DOMContentLoaded', function(e) {
      doc.body.style.fontSize = 12 * dpr + 'px';
    }, false);
  }
   
 
  refreshRem();
 
  flexible.dpr = win.dpr = dpr;
  flexible.refreshRem = refreshRem;
  flexible.rem2px = function(d) {
    var val = parseFloat(d) * this.rem;
    if (typeof d === 'string' && d.match(/rem$/)) {
      val += 'px';
    }
    return val;
  }
  flexible.px2rem = function(d) {
    var val = parseFloat(d) / this.rem;
    if (typeof d === 'string' && d.match(/px$/)) {
      val += 'rem';
    }
    return val;
  }
 
})(window, window['lib'] || (window['lib'] = {}));

rem布局计算与换算方法

上述代码的核心逻辑在于修改dpx及document的font-size属性,其计算公式为:docEl.getBoundingClientRect().width / 10 + 'px'。

以宽度为640px的设计稿为例,HTML的字体大小会被设置为64px。这意味着1rem等于64px(即相对于根节点字体大小的倍数)。

若一个元素在设计稿中的宽度为160px,传统做法可能需要通过百分比或响应式媒体查询在不同分辨率(如320px输出80px,640px输出160px)下分别设置。而采用Flexible.js方案,只需统一输出2.5rem即可实现自适应,具体对应关系如下表所示:

设备宽度 320px 360px 414px 640px
Html字体大小 32px 36px 41.4px 64px
实际输出 1rem 1rem 1rem 1rem
设计稿缩放大小 80px 90px 103.5px 160px
实际输出 2.5rem 2.5rem 2.5rem 2.5rem

上述2.5rem数值是如何计算得出的?

计算方法为:160 / 64(此处1rem基准值为64px)= 2.5rem。根据官方文档的解释(以750px设计稿为例):

Flexible会将视觉稿宽度划分为100份,这一设计主要为了未来更好地兼容vh和vw单位。每一份被称为一个单位a,同时规定1rem等于10a。针对上述750px视觉稿,可计算出:

1a = 7.5px 1rem = 75px

因此,该示例的设计稿总宽度被划分为10a,即整个页面宽度为10rem,对应的<html>元素font-size设置为75px。

由此,视觉稿上元素尺寸的换算变得简单:只需将原始px值除以rem基准值即可。例如,视觉稿中一张176px * 176px的图片,转换为rem单位即为2.346667rem * 2.346667rem。

开发注意事项与字体处理方案

此外,使用Flexible处理自适应时需注意CSS Sprite的问题。作者建议使用SVG、Icon Font或Base64等其他方案替代。

另外,当设备像素比dpr=2时,小图片可能会出现模糊现象,建议以最大图片的尺寸进行切图处理。

关于字体单位,建议使用px。

作者观点认为,描述性字体应使用px单位。对于Slogan等大于48px的文本,可以使用rem。由于使用rem会导致iPhone5和iPhone6中字体大小不同(可能出现13px和15px等点阵字体尺寸),因此需谨慎选择。

显然,在iPhone3G和iPhone4的Retina屏幕下,我们希望看到的文本字号保持一致。也就是说,我们不希望文本在Retina屏幕下变小,同时希望在大屏手机上显示更多文本。此外,绝大多数字体文件自带点阵尺寸(通常为16px和24px),因此我们应避免出现13px和15px等非标准尺寸。

基于上述原因,在制作H5页面时,rem并不适合用于段落文本。因此,Flexible适配方案建议文本仍使用px单位,但通过[data-dpr]属性来区分不同dpr下的文本字号大小。


div {
  width: 1rem; 
  height: 0.4rem;
  font-size: 12px; // 默认写上dpr为1的fontSize
}
[data-dpr="2"] div {
  font-size: 24px;
}
[data-dpr="3"] div {
  font-size: 36px;
}

为了便于开发,我们可以定制一个名为font-dpr()的Sass混合宏:


@mixin font-dpr($font-size){
  font-size: $font-size;

  [data-dpr="2"] & {
    font-size: $font-size * 2;
  }

  [data-dpr="3"] & {
    font-size: $font-size * 3;
  }
}

使用该混合宏后,在开发中可直接按如下方式使用:

@include font-dpr(16px);

当然,这仅适用于描述性文本(如段落文本)。但在某些场景下,文本字号也需要分场景处理。例如项目中有一个Slogan,业务方希望其能根据不同终端进行适配。针对此类场景,完全可以使用rem作为Slogan的计量单位。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章