libflexible源碼閱讀

前言

最近須要一款移動端的產品,當時須要趕工期,在參考了天貓的flexbox佈局和手淘的
rem佈局方案後,決定選用libflexible。作完項目以後,稍有空閒時間,決定看看libflexible
如何實現動態設置根元素的字體,從而經過rem的方式改變其餘元素大小javascript

正文

首先咱們看一下flexible須要哪些屬性css

;(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 = {});

在函數運行時,咱們須要將windows對象注入,而後在看看windows下是否已經存在lib(便是否已經有
使用過flexible)了,注入完以後,咱們依次得到doc,docEl,metaEl,flexibleEl,其中metaEl
是爲了判斷咱們是否已經有預設好的viewport,flexibleEl則是判斷咱們已經手動設置好dpr來避免
flexible庫動態設置dprhtml

if (metaEl) {
     console.warn('將根據已有的meta標籤來設置縮放比例');
     var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
     if (match) {
         scale = parseFloat(match[1]);
         dpr = parseInt(1 / scale);
     }
 }

若是metaEl存在的話,意味着頁面上存在形如<meta name="viewport" content="initial-scale=1">的標籤,此時咱們已經明確了咱們須要的縮放,再也不須要flexible的介入,縮放值scale直接
使用預設的initial-scale,經過咱們預設的的縮放,咱們的layout viewport將會是 ideal viewport/scale,若是咱們的initual-scale爲1的話
且ideal爲414px的話,咱們的layout viewport將會是414pxjava

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));
           }
       }
   }

若是咱們沒有設置初始的viewport可是有<meta name="flexible" content="initial-dpr=2" />這樣的flexible自身的預設
那麼咱們將會有預設的dpr,此時flexible將根據咱們預設的dpr經過scale=1/dpr的方式來計算出咱們的縮放,進而影響layout viewport
的大小android

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;
   }

接下來,若是咱們既沒有經過<meta name="flexible" content="initial-dpr=2" />這種方式預設dpr,
也沒有經過<meta name="viewport" content="initial-scale=1">的方式預設縮放,此時flexible開始根據
設備的dpr來動態計算縮放。對於非蘋果設備,flexible設置dpr爲1,對於蘋果設備,iPhone3如下非retina屏,dpr爲1
iPhone4-iPhone6爲retina屏,dpr爲2,iPhone6Plus爲retina HD屏,dpr爲3,因爲flexible是一個專一於移動端
的解決方案,因此平板(包括iPad)或者桌面端的dpr都爲1windows

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);
     }
 }

計算完縮放後,將獲取的dpr值設置到根元素上,這樣咱們就能夠經過如下方式:緩存

.selector {
    width: 2rem;
    border: 1px solid #ddd;
}
[data-dpr="1"] .selector {
    height: 32px;
    font-size: 14px;
}
[data-dpr="2"] .selector {
    height: 64px;
    font-size: 28px;
}
[data-dpr="3"] .selector {
    height: 96px;
    font-size: 42px;
}

爲不一樣dpr的屏幕設置不一樣的字體大小,字體之因此不用rem佈局,是由於app

咱們不但願文本在Retina屏幕下變小,另外,咱們但願在大屏手機上看到更多文本,以及,如今絕大多數的字體文件都自帶一些點陣尺寸,一般是16px和24px,因此咱們不但願出現13px和15px這樣的奇葩尺寸。iphone

同時當不存在metaEl時,flexible動態生成一條<meta name="viewport" content="initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale},user-scalable=no">
的標籤,若是<html>下存在元素(如<head>等元素)那麼,將meta標籤插入,若是沒有,就將meta標籤用一個div包裝,而後
經過document.write寫入到文檔中ide

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;
   }

好了,縮放和dpr都設置好了,下一步咱們要設置根元素的font-size了,這樣咱們能夠經過rem的方式
適配不一樣屏幕。首先咱們須要經過docEl.getBoundingClientRect().width得到layout viewport的寬度,
而後將layout viewport寬度分爲10份,1份爲1rem。至於設置540px,是爲了讓在ipad橫屏這種狀況下瀏覽頁面,不至於由於拉伸適配後體驗太差。固然這仍是有一點點問題的
由於這樣10rem將不會是ipad的滿屏了。固然這是移動端解決方案,並無考慮平板和桌面端

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);
   }

body上設置12 * dpr的font-size值,爲了重置頁面中的字體默認值,否則沒有設置font-size的元素會繼承html上的font-size,變得很大。

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;
        }

最後,flexible提供了兩個工具函數px2rem和rem2px,這裏就不在闡述了

總結

看完flexible,實實在在的感覺到rem佈局的妙處,經過動態設置縮放係數的方式,讓layout viewport與設計圖對應,極大地方便了重構,同時也避免了1px的問題,然而略有遺憾的就是在安卓和ipad上的表現不佳了。

相關文章
相關標籤/搜索