經過前幾天寫的兩篇博客(淺談移動端三大viewport和移動端em和rem區別),咱們如今來總結一下如何實現一個最佳方案。css
以前在第二篇博客中提到過咱們可使用媒體查詢來針對不一樣設備及作適配,以下圖html
可是這個可能不會太精準,好比個人設備佈局viewport多是370px,這樣只能使用320這一版本。而事實上,他們的viewport並不相同,因此他們的佈局也得不同。也就是說咱們若是用css媒體查詢只能說是能夠用,但不是最佳實踐。其實經過看某些牛逼的移動端網站,能夠看到他們的共同點,他們都是使用js方式來實現的。android
能夠看到,慕課網以及咱們如今作的今日十大H5頁面,他們共同的地方就是在根html上設置data-dpr以及font-size。git
爲何要設置這兩個東西呢?github
咱們能夠迴歸移動端的本質,就是根據不一樣的設備規劃不一樣的佈局,這裏面有一點須要注意,就是任何分辨率下咱們的字體要保持一致。因此data-dpr是針對字體設置的,font-size是咱們不一樣設備上用rem的基準值。其實有一個規範的js文件能夠幫咱們解決以上問題,flexible.js(能夠到github上copy)基本知足咱們的需求。瀏覽器
;(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'] = {}));
簡單的說一下這裏面的重點吧,咱們先設置頁面viewport中的scale,這個用來解決border 1px問題,下面是個人一個css demosass
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <title>Document</title> <style> ul li { width: 100px; padding: 8px 0 8px 15px; color: #7c7c7c; position: relative; } ul li + li:before { position: absolute; top: -1px; left: 0; content: ''; width: 100%; height: 1px; border-top: 1px solid #ddd; /*transform: scaleY(0.5);*/ } </style> </head> <body> <ul> <li></li> <li></li> <li></li> </ul> </body> </html>
而後咱們下面展現的樣式第一個是註釋掉 transform: scaleY(0.5)的,第二個是打開的。app
爲何會這樣子呢,我在淺談移動端三大viewport說過,dpr爲2的高清屏下,1個css像素=4個物理像素(1px = dpr²*dp)ssh
因此,設計師想要的retina下border: 1px;
其實就是1物理像素寬,對於css而言,能夠認爲是border: 0.5px;
,這是高清屏下(dpr=2)下能顯示的最小單位,但並非全部的瀏覽器都支持border:0.5px,因此咱們須要scale(0.5),也就是上面flexible.js提到的scale,經過不一樣的dpr(devicePixelRatio)來設置不一樣的scale。iphone
而後咱們的主角rem是經過refreshRem函數中的docEl.getBoundingClientRect().width來獲取的,這個值跟document.body.clientWidth是相同的,也就是佈局viewport,以後的width/10純屬是爲了便於計算。等下,當咱們在iphone5下查看佈局viewport時,爲何是640,而不是320,
這是由於咱們在前面設置了scale,在iphone5下,經過獲取dpr(devicePixelRatio)是2,而後計算出initial-scale是0.5,由於在initial-scale爲1狀況下,咱們知道佈局viewport等於設備寬度,也就是等於理想視口(screen.width),iphone5下的理想視口是320,而後縮放比是0.5,因此最終的佈局viewport是640。移動端大神PPK曾經說initial-scale是理想視口與視覺視口之比,但我感受既然設置了initial-scale,咱們的視覺視口與佈局視口就相等了,因此咱們在計算時用的document.body.clientWidth,用window.innerWidth(視覺視口)其結果也同樣,只不過是爲了給你們演示。最後根據咱們計算出來的html的基值能夠在css編碼中還原視覺稿的真實寬高。
好比咱們拿到一個iphone6的高清設計稿(750×1334),在psd中量出一個div寬爲750,如何轉換爲rem單位呢?
公式—— rem = px / 基值
經過flexible.js計算根html的font-size爲75px,因此咱們能夠這樣寫css
div {
width: 10rem;
}
轉換成html就是這樣
width: 10rem; // -> 750px
最後由於dpr爲2,scale了0.5,因此手機屏幕上的真實寬度爲375px,就剛恰好。試想一下,假如咱們不用initial-scale=0.5,那樣咱們得在這個設計稿上量完div後,再口算除以2,纔會寫width=375px,想一想就累。
最後說一下字體的設置吧,這個比較特殊。如下分別是咱們如今作的項目(今日十大)中不一樣設備的字體展現,能夠清楚地看到任何手機屏幕上字體大小都要統一。
固然爲了方便,咱們使用sass作了一次遍歷。在不一樣的data-dpr中遍歷font-size(這裏咱們就看到了以前html中data-dpr的做用了)