flexible源码详解(@0.3.2)
有关文章中不理解的移动端适配的概念,建议大家先去储备一些移动端适配方面的知识,当然,我之后也会做一个比较详尽的移动端适配方案的总结。
-
最近在看有关移动端适配的东西,lib-flexible是一款比较经典的适配框架,为了彻底的理解他,看了一下它的源码。我会在这里做逐段的分析,这里有完整的源码,大家可以参考一下。有任何意见和建议,希望大家积极讨论。
-
说回flexible,它的主要思想还是使用rem单位,根据屏幕大小的变化,动态改变根字体的大小,并且识别设备的dpr,动态配置出适合当前环境的viewport配置。下面我们看下它的源码
-
第一部分就是统一定义后面要用到的变量,都比较好理解
var doc = win.document; //定义文档的根节点 var docEl = doc.documentElement; //查询符合条件的meta标签 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 = {});
-
紧接着是对metaEl的判断,一般情况下,我们都会在html文件中做这样的设置。
<meta name="viewport" content="width=device-width, initial-scale=1.0">
-
如果我们已经做了这样的设置,flexible将会根据我们meta标签内的配置,对dpr和scale赋值,flexibleEl同理。我个人建议,如果要使用flexible,就不要在html做手动的配置,除非有特殊的要求。
if (metaEl) { console.warn('将根据已有的meta标签来设置缩放比例'); //这里会把meta标签的content属性内容转化为一个数组 var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/); //如果我们的content中有值 if (match) { //按照默认设置,这里的dpr和scale都为1 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)); } } }
-
为什么不建议在html中配置上述的meta,原因就在这里,如果执行了以上代码,dpr和scale都已经存在值,那么下面的判断和一系列操作将不会执行。这里还有一个非常诡异的点,既然定义安卓手机的判断标准,但是并没有做出有效的区分,只是把iPhone以外的手机统一处理,可能当时的安卓机并没有dpr>=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; }
-
代码执行至此,dpr和scale都已经有了值,首先,flexible会在html标签上创建“data-dpr”来标识当前设备的dpr,其次,如果到了这里还没有meta标签的配置,flexible会帮我们自动创建meta标签。
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'); //这里就是判断是否存在<head>标签 if (docEl.firstElementChild) { docEl.firstElementChild.appendChild(metaEl); } else { var wrap = doc.createElement('div'); wrap.appendChild(metaEl); doc.write(wrap.innerHTML); } }
-
下面就是flexible能实时响应的核心,在项目初次运行、页面展示、页面大小改变的时候都会执行refreshRem()方法。这个方法中有很多有趣的设置。
function refreshRem(){ //这里的width通俗理解就是页面缩放前的宽度 var width = docEl.getBoundingClientRect().width; //width超过540时达到最大值,会影响到之后根字体的最大值。 if (width / dpr > 540) { width = 540 * dpr; } //flexible中对rem的计算方式,除以十我认为是为了方便计算 var rem = width / 10; docEl.style.fontSize = rem + 'px'; flexible.rem = win.rem = rem; } //监听页面的变化,触发refreshRem方法 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);
-
这里对body的操作,一开始我也不理解,其实是要清楚flexible使用的方法,它建议我们各类组件和容器的宽高布局使用rem单位,而对于字体使用px,这下就明白了,body中的fontsize是用来控制字体大小了,而不会影响rem的使用。
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); }
-
那么为什么要针对字体使用px单位呢?我个人的理解是flexible不希望字体的大小自适应变化,可能会影响美观,或者考虑到浏览器对最小字号的规定不统一。当然,在flexible中字体使用rem也是可以的。
flexible的原理大概就是这些,当然它会存在很多问题,需要搭配类似postcss-px2rem的插件去使用。也许他已经不是最完美的适配方案,但是flexible在移动端适配的道路上,起到了良好的过渡作用。