最近須要一款移動端的產品,當時須要趕工期,在參考了天貓的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上的表現不佳了。