1. 程式人生 > >面試官 | 說說移動端專案適配

面試官 | 說說移動端專案適配

## 前置 希望通過這篇文章幫助你很好的適配移動端專案,如有不足,懇請指點一二! ## 單位 - 解析度: 單位面積顯示畫素的數量,和 css 無關 - DPI:影象每英寸長度內的畫素點數(1 英尺=30.48 釐米) - css 的 px: 96 DPI 的單畫素的物理大小 - 物理畫素:在由一個數字序列表示的影象中的一個最小單位 - dpr: 一個 CSS 畫素的大小相對於一個物理畫素的大小的比值 - rem: 1rem = 根元素的字型大小 \* 1 《css 權威指南》對於 css 畫素的解釋: ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/css-px.jpg) ## 基準值 ```css html { font-size: 12px; } div { width: 2rem; } ``` 這裡根元素的字型大小 12px,稱之為**基準值**。這時 div 的 width 為 `2rem = 12px\*2 = 24px`. 通常設計稿都以 px 為單位,如果一個塊的高度為 50px,轉換為 rem 非常簡單,即 `50px/12px rem`。如果在不同螢幕下設定不同基準值,就能實現簡單的伸縮適配的目的了。 ## 設計搞 移動端設計稿通常以 iphone6 螢幕為基準。iphone6 螢幕寬度為 375px,在進行適配時通常以 37.5px 為**基準值**,為什麼不直接以 375px 為基準值呢?因為這個值太大了,所以除以 10。為什麼要以螢幕寬度為基準值呢?為了方便適配,在 iphone4/5 下以 `320px / 10 = 32px` 作為基準值就好了,其他裝置類比。 ## 如何做 在不同螢幕下設定不同基準值,就能實現縮放適配的目的。以下幾種方法都能實現。 - 利用 css 媒體查詢(Media Queries) ```css @media (min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) { html { font-size: 37.5px; } } /* ... */ ``` - 利用簡易的 JavaScript ```js document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px' ``` - 使用 flexible.js,這是當前常用的方式 ## flexible ### amfe-flexible 1.使用 - [下載 js 檔案](https://github.com/amfe/lib-flexible?utm_source=caibaojian.com)通過內聯的方式引入 - 使用 npm `npm i amfe-flexible` 2.如果使用 webpack 構建專案,使用 [px2rem-loader](https://www.npmjs.com/package/px2rem-loader)。這樣設計稿上元素是多少 px 就寫多少 px 。注意:要將基準值設定為 37.5 的二倍 75,因為設計稿的寬度一般是 iphone6 寬度的二倍。loader 配置: ```js { loader: 'px2rem-loader', options: { remUni: 75, remPrecision: 8 } } ``` 如果直接引入的 js,可以使用 vscode 擴充套件 [px2rem](https://marketplace.visualstudio.com/items?itemName=arturiapendragon.px2rem),同樣你需要開啟 vscode settings 並設定基準值。書寫時設計稿上元素是多少 px 就寫多少 px,外掛會幫你在書寫時自動轉換成對應的 rem。 ```js "px2rem.rootFontSize": 75, "px2rem.fixedDigits": 6, ```
原始碼解釋 ```js ;(function flexible(window, document) { var docEl = document.documentElement var dpr = window.devicePixelRatio || 1 // Document.documentElement: 是一個會返回文件物件(document)的根元素的只讀屬性(如HTML文件的 元素)。 // window.devicePixelRatio(dpr): 返回當前顯示裝置的物理畫素解析度與CSS畫素解析度的比值。該值也可以被解釋為畫素大小的比例:即一個CSS畫素的大小相對於一個物理畫素的大小的比值。 // 調整 body 字型大小 function setBodyFontSize() { if (document.body) { document.body.style.fontSize = 12 * dpr + 'px' } else { document.addEventListener('DOMContentLoaded', setBodyFontSize) // DOMContentLoaded:當純HTML被完全載入以及解析時,DOMContentLoaded 事件會被觸發 } } setBodyFontSize() // 設定基準值 // 1rem = clientWidth / 10 function setRemUnit() { var rem = docEl.clientWidth / 10 docEl.style.fontSize = rem + 'px' } setRemUnit() // 調整頁面大小時重置 rem window.addEventListener('resize', setRemUnit) window.addEventListener('pageshow', function (e) { // pageshow:當一條會話歷史記錄被執行的時候將會觸發頁面顯示(pageshow)事件。(這包括了後退/前進按鈕操作,同時也會在onload 事件觸發後初始化頁面時觸發) if (e.persisted) { // persisted:網頁是否來自快取 setRemUnit() } }) // 檢測 0.5px 支援 if (dpr >= 2) { var fakeBody = document.createElement('body') var testElement = document.createElement('div') testElement.style.border = '.5px solid transparent' // transparent是全透明黑色(black)的速記法,即一個類似rgba(0,0,0,0)這樣的值。 fakeBody.appendChild(testElement) docEl.appendChild(fakeBody) if (testElement.offsetHeight === 1) { docEl.classList.add('hairlines') } docEl.removeChild(fakeBody) } })(window, document) ```
### lib-flxible lib-flxible 是更為完善的版本。 ``` npm i lib-flxible ```
原始碼 ```js ;(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'] = {})) ```
缺陷 - 建議字型大小依然使用 px,由於使用 rem 將導致在不同移動裝置中字型大小不同,可能出現 13px 和 15px 這樣奇怪的字型尺寸 - 難以處理[點陣字型](https://baike.baidu.com/item/%E7%82%B9%E9%98%B5%E5%AD%97%E4%BD%93/6996829?fr=aladdin),點陣字型也叫點陣圖字型。由於點陣圖的緣故,點陣字型很難進行縮放,特定的點陣字型只能清晰地顯示在相應的字號下,否則文字只被強行放大而失真字形,產生成馬賽克式的鋸齒邊緣。 那麼如何適配字型呢?當你不使用任何 css 前處理器時這樣做: ```css .item { font-size: 12px; } [data-dpr='2'] .item { font-size: 24px; } [data-dpr='3'] .item { font-size: 36px; } ``` 如果使用 scss,其他 css 前處理器類比即可: ```css // 定義一個混合巨集(或者使用函式),在設定字型大小時直接引入即可 @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; } } .item { @include font-dpr(16px); } ``` > 除此之外,可能會在移動端專案中出現很少見的大標題,完全可以直接使用 rem,因為我們希望這樣的大標題也能跟隨不同的裝置進行縮放。而不是使用 px,在較小尺寸的裝置由於標題過大而顯示不全(處理成...)或者換行。 ## 使用 viewport ![](https://gitee.com/guangzan/imagehost/raw/master/markdown/caniuse-viewport.jpg) 使用 flexible.js 我們需要在專案中嵌入 JavaScript,讓我們嘗試一種新的方式。 ### 視口單位 - vw : 1vw 等於視口寬度的 1% - vh : 1vh 等於視口高度的 1% - vmin : 選取 vw 和 vh 中最小的那個 - vmax : 選取 vw 和 vh 中最大的那個 看到這裡你可能對 vmin 和 vmax 有些疑問,這裡給出一小段解釋你會豁然開朗:使用 vw、wh 設定字型大小在豎屏和橫屏狀態下顯示的字型大小是不一樣的,使用 vmin 和 vmax 使得文字大小在橫豎屏下保持一致。 > 注:IE9 僅支援使用 vm 代替 vmin,IE 6-11 均不支援 vmax。 ### 第一種方式 上文已經說過設計稿通常以 iphone6(375) 的尺寸作為基準。如果使用 scss: ```css @function vw($px) { @return ($px / 375) * 100vw; } ``` 在使用時僅將設計稿上對應的 px 傳入函式即可: ```css .item { padding: vw(15) vw(10) vw(10); width: vw(40); font-size: vw(10); } ``` > Tip: 不用裝置的寬度變化會導致自動伸縮,伸縮佈局就實現了。 ### 第二種方式 具體思路:給根元素打字型大小使用單位 vw, 其他地方使用 rem。裝置寬度變化 -> vw 實際渲染出來的大小改變 = 根元素字型大小改變 -> rem 實際渲染出來的大小變換 = 全域性根據裝置寬度縮放。看到這裡你又會有疑問了,vw 本身就能縮放了,何必再繞上一圈?下面有一個例子,假設設計稿依然以 iphone6 尺寸作為基準:
例子 ```css @function rem($px) { @return ($px / 75) * 1rem; } html { font-size: (75 / (750 / 2)) * 100vw; @media screen and (max-width: 320px) { font-size: 64px; } @media screen and (min-width: 540px) { font-size: 108px; } } .item { width: rem(50); font-size: rem(10); } ```
看完這段程式碼你就明白了,原來我們利用 rem 只會根據根元素字型大小進行縮放這一特點,給根元素通過媒體查詢限制其字型的最大值與最小值,就能在全域性實現修復一些元素的過大或過小產生的瑕疵。同時,這樣做還有一個好處,它能使原來使用 flexible.js 的專案輕易地遷移到 vw+rem。