移動互聯網快速發展的今天,手機的種類和尺寸愈來愈多,做爲前端的小夥伴們可能會愈來愈頭疼,但又不得不去適配一款又一款的新機型。對於移動端適配,不一樣的公司、不一樣的團隊有不一樣的解決方案。我在項目中也用了一部分解決方案,也看到了一些解決方案,對比下,總結一些本身的理解,但願對各位有幫助,找到最適合大家項目的適配方案。javascript
屏幕的物理像素,又被稱爲設備像素,他是顯示設備中一個最微小的物理部件。任何設備屏幕的物理像素出廠時就肯定了,且固定不變的。css
設備獨立像素也稱爲密度無關像素,能夠認爲是計算機座標系統中的一個點,這個點表明一個能夠由程序使用的虛擬像素(好比說CSS像素),而後由相關係統轉換爲物理像素。html
設備像素比簡稱爲dpr,其定義了物理像素和設備獨立像素的對應關係前端
設備像素比 = 物理像素 / 設備獨立像素 以iphone6爲例: iphone6的設備寬和高爲375pt * 667pt,能夠理解爲設備的獨立像素,而其設備像素比爲2.固有設備像素爲750pt * 1334pt
經過:window.devicePixelRatio得到。html5
設備像素比是區別是不是高清屏的標準,dpr大於1時就爲高清屏,通常狀況下dpr爲整數,可是android有些奇葩機型不爲整數。java
在CSS、JS中使用的一個長度單位。單位pxandroid
注:在pc端1物理像素等於1px,可是移動端1物理像素不必定等於1px,1物理像素與px的關係與如下因素有關。(有些視口概念,能夠把下面視口看完了再來看)ios
一、屏幕布局視口大小(下面會講到) 二、屏幕的分辨率(物理像素)
對於一塊屏幕,其物理像素是肯定的。視覺視口尺寸是繼承的佈局視口的,而視覺視口裏寬度便是css的px數。故在一塊屏上物理像素與px的關係就是物理像素與佈局視口的px數的關係。web
好比iphone6,期物理像素爲750,若是沒有設置佈局視口時,viewport爲980px
此時:1物理像素長度等於980/750px = 1.3067px的長度
因爲像素都是點陣的,故1物理像素至關於1.3067px * 1.3067px方格。
當在meta中設置了以下配置時
<meta name="viewport" content="width=device-width"> 至關於把佈局視口設置爲設備的寬度(即上面講到的設備獨立像素), 對於iphone6就是375px。 此時1物理像素長度等於375/750px = 0.5px的長度,故1物理像素至關於0.5px * 0.5px的方格。
在html中通常在meta中的name爲viewport字段就是控制的佈局視口。佈局視口通常都是瀏覽器廠商給的一個值。在手機互聯網沒有普及前,網絡上絕大部分頁面都是爲電腦端瀏覽而作的,根本沒有作移動端的適配。隨着移動端的發展,在手機上看電腦端的頁面已成爲很是普及現象。而電腦端頁面寬度較大,移動端寬度有限,要想看到整個網頁,會有很長的滾動條,看起來很是麻煩。因而瀏覽器廠商爲了讓用戶在小屏幕下網頁也可以顯示地很好,因此把佈局視口設置的很大,通常在768px ~ 1024px 之間,最經常使用的寬度就是 980。這樣用戶就能看到絕大部份內容,並根據具體內容選擇縮放。瀏覽器
故佈局視口是看不見的,瀏覽器廠商設置的一個固定值,如980px,並將980px的內容縮放到手機屏內。
佈局視口能夠經過:
document.documentElement.clientWidth(clientHeight) // 佈局視口的尺寸。
瀏覽器可視區域的大小,即用戶看到的網頁的區域。(其寬度繼承的佈局視口寬度)
window.innerWidth(innerHeight) // 視覺視口尺寸
佈局視口雖然解決了移動端查看pc端網頁的問題,可是徹底忽略了手機自己的尺寸。因此蘋果引入了理想視口,它對設備來講是最理想的佈局視口,用戶不須要對頁面進行縮放就能完美的顯示整個頁面。最簡單的作法就是使佈局視口寬度改爲屏幕的寬度。
能夠經過window.screen.width獲取。
<meta name="viewport" content="width=device-width">
移動端到底怎麼適配不一樣的屏幕呢?最簡單的方法是設置以下視口:
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
當使用以上方案定義佈局視口時,即佈局視口等於理想視口(屏幕寬度),屏幕沒有滾動條,不存在高清屏下,字體較小的問題。可是在不一樣屏幕上,其視覺寬度是不一樣的,不能簡單的將全部的尺寸都設置爲px,可能會出現滾動條。小尺寸的能夠用px,大尺寸的只能用百分比和彈性佈局。
對於上面的設置,再不一樣的屏幕上,css像素對應的物理像素具數是不一致的。
在普通屏幕下,dpr=1時,
1個css像素長度對應1個物理像素長度,1個css像素對應1個物理像素。
而在Retina屏幕下,若是dpr=2,
1個css像素長度對應2個物理像素長度,1css像素對應4個物理像素。
此時若是css中寫
border: 1px solid red; // 此時1px 對應的寬度是2物理像素的寬度。
而通常如今移動端設計稿都是基於iphone設計的,稿子通常爲750px或640px,這正好是iphone6和iphone5的物理像素。在設計稿中,通常有些邊框效果,這時邊框的線寬爲1px,對應的就是1物理像素。而對於iphone5和iphone6,當width=device-width時,css的1px顯示出來的是2個物理像素,因此看起來線就比較粗。怎麼解決呢?1px邊框效果其實有不少hack方法,其中一種就是經過縮放viewport。
initial-scale是將佈局視口進行縮放,initial-scale是相對於理想視口的,即initial-scale=1與width=device-width是同樣的效果。initial-scale=0.5等效於width= 2倍的device-width,因此設置initial-scale和width均可以改變佈局視口的大小。
<meta name="viewport" content="width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
對於iphone6當添加如上設置後,initial-scale=0.5,即將頁面縮小2倍後等於屏幕寬度。
佈局視口width: width / 2 = 375px; width = 750px;
因此此時佈局視口爲750px,此時1px等於1物理像素。
上面講了一些基礎概念,下面講具體適配。
對於ui設計師給的一張設計稿,怎麼將其還原到頁面上?對於不一樣手機屏幕,其dpr不一樣,屏幕尺寸也不一樣,考慮到各類狀況,有不少適配方案,因此不一樣的適配方案,實現方法不一樣,處理複雜度也不一樣,還原程度也不一樣。
方案一:
固定高度,寬度自適應。
這種方案是目前使用較多的方案,也是相對較簡單的實現方案:
該方法使用了理想視口:
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
垂直方向使用固定的值,水平方向使用彈性佈局,元素採用定值、百分比、flex佈局等。這種方案相對簡單,還原度也很是低。
方案二:
固定佈局視口寬度,使用viewport進行縮放
荔枝的代碼:
if(/Android (\d+\.\d+)/.test(navigator.userAgent)){ var version = parseFloat(RegExp.$1); if(version>2.3){ var phoneScale = parseInt(window.screen.width)/640; if(/MZ-M571C/.test(navigator.userAgent)){ document.write('<meta name="viewport" content="width=640, minimum-scale = 0.5, maximum-scale= 0.5">'); }else if(/M571C/.test(navigator.userAgent)&&/LizhiFM/.test(navigator.userAgent)){ document.write('<meta name="viewport" content="width=640, minimum-scale = 0.5, maximum-scale= 0.5">'); }else{ document.write('<meta name="viewport" content="width=640, minimum-scale = '+ phoneScale +', maximum-scale = '+ phoneScale +', target-densitydpi=device-dpi">'); } }else{ document.write('<meta name="viewport" content="width=640, target-densitydpi=device-dpi">'); } }else{ document.write('<meta name="viewport" content="width=640, user-scalable=no, target-densitydpi=device-dpi">'); }
網易應用:
var win = window,
width = 640, iw = win.innerWidth || width, ow = win.outerHeight || iw, sw = win.screen.width || iw, saw = win.screen.availWidth || iw, ih = win.innerHeight || width, oh = win.outerHeight || ih, ish = win.screen.height || ih, sah = win.screen.availHeight || ih, w = Math.min(iw, ow, sw, saw, ih, oh, ish, sah), ratio = w / width, dpr = win.devicePixelRatio; if (ratio = Math.min(ratio, dpr), 1 > ratio) { var ctt = ",initial-scale=" + ratio + ",maximum-scale=" + ratio, metas = document.getElementsByTagName("meta");ctt += ""; for (var i = 0, meta; i < metas.length; i++) meta = metas[i], "viewport" == meta.name && (meta.content += ctt) }
固定佈局視口,寬度設置固定的值,總寬度爲640px,根據屏幕寬度動態生成viewport。(設計稿應該是640px的)
<meta name="viewport" content="width=640, minimum-scale = 0.5625, maximum-scale = 0.5625, target-densitydpi=device-dpi">
這種方式佈局如荔枝FM的網頁寬度始終爲640px。縮放比例scale爲:
var scale = window.screen.width / 640
設計稿爲640px時,正好能夠1:1以px來寫樣式。可是1px所對應的物理像素就不必定是1了。
(window.screen.width * dpr) / 640 // 1px對應的物理像素
方案三:
根據不一樣屏幕動態寫入font-size,以rem做爲寬度單位,固定佈局視口。
如網易新聞:
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
以640px設計稿和750px的視覺稿,網易這樣處理的:
var width = document.documentElement.clientWidth; // 屏幕的佈局視口寬度 var rem = width / 7.5; // 750px設計稿將佈局視口分爲7.5份 var rem = width / 6.4; // 640px設計稿將佈局視口分爲6.4份
這樣不論是750px設計稿仍是640px設計稿,1rem 等於設計稿上的100px。故px轉換rem時:
rem = px * 0.01;
在750px設計稿上:
75px 對應 0.75rem, 距離佔設計稿的10%; 在ipone6上: width = document.documentElement.clientWidth = 375px; rem = 375px / 7.5 = 50px; 0.75rem = 37.5px; (37.5/375=10%;佔屏幕10%) 在ipone5上: width = document.documentElement.clientWidth = 320px; rem = 320px / 7.5 = 42.667px; 0.75rem = 32px; (32/320=10%;佔屏幕10%)
故對於設計稿上任何一個尺寸換成rem後,在任何屏下對應的尺寸佔屏幕寬度的百分比相同。故這種佈局能夠百分比還原設計圖。
方案四:
以rem做爲寬度單位,動態寫入viewport和font-size進行縮放。
根據設置的dpr設置font-size。如:
document.documentElement.style.fontSize = 50 * dpr; // dpr 爲設置的設備像素比。(注意不是設備自身的設備像素比,而是認爲設置的dpr)
這種狀況下,dpr = 1時,1rem = 50px;
dpr = 2時, 1rem = 100px;
當設計以iphone6爲標準,出750px的設計稿時,此時dpr=2,故1rem 等於100px,將圖上的尺寸轉換爲rem很是方便,除以100就行。代碼以下:
var scale = 1.0; var dpr = 1; var isAndroid = window.navigator.appVersion.match(/android/gi); var isIPhone = window.navigator.appVersion.match(/iphone/gi); var devicePixelRatio = window.devicePixelRatio; // 此處只簡單對ios作了伸縮處理,安卓沒有作伸縮處理,統一dpr = 1 if ( isIPhone ) { scale /= devicePixelRatio; dpr *= devicePixelRatio; } var viewport = document.getElementById('viewport'); var content = 'initial-scale=' + scale + ', maximum-scale=' + scale + ',minimum-scale=' + scale + ', width=device-width, user-scalable=no'; viewport.setAttribute( 'content', content ); document.documentElement.style.fontSize = 50 * dpr + 'px'; document.documentElement.setAttribute('data-dpr', dpr);
對於該方案,
假設肉眼看到的寬度(視覺寬度):visualWidth,令dpr=1時,其1rem對應的寬度爲50. dpr = 1 時, 1rem = 50px, initial-scale=1, 縮放爲1。 visualWidth = 50 * 1 = 50; dpr = 2 時, 1rem = 100px, initial-scale=0.5, 縮放爲0.5。 visualWidth = 100 * 0.5 = 50; dpr = 3 時, 1rem = 150px, initial-scale=0.3333, 縮放爲0.3333。 visualWidth = 150 * 0.3333 = 50;
因此該方案,1rem在全部屏幕上對應的肉眼距離相同,故不一樣屏幕下,總的rem數不一樣,大屏下總的rem數大於小屏下,如iphone6下,總寬度爲7.5rem,iphone5下,總寬度爲6.4rem。故此方案不能百分比還原設計稿,故寫樣式時,對於大塊元素應該用百分比,flex等佈局,不能直接用rem。
關於這個方案的詳細教程請參考這篇文章傳送門
方案五:
根據不一樣屏幕動態寫入font-size和viewport,以rem做爲寬度單位
將屏幕分爲固定的塊數10:
var width = document.documentElement.clientWidth; // 屏幕的佈局視口寬度 var rem = width / 10; // 將佈局視口分爲10份
這樣在任何屏幕下,總長度都爲10rem。1rem對應的值也不固定,與屏幕的佈局視口寬度有關。
對於動態生成viewport,他們原理差很少,根據dpr來設置縮放。看看淘寶的:
var devicePixelRatio = window.devicePixelRatio; var isIPhone = window.navigator.appVersion.match(/iphone/gi); var dpr,scale; if (isIPhone) { if (devicePixelRatio >=3) { dpr = 3; } else if (devicePixelRatio >=2) { dpr = 2; } else { dpr = 1; } } else { dpr = 1; } scale = 1 / dpr;
淘寶只對iphone作了縮放處理,對於android全部dpr=1,scale=1即沒有縮放處理。
此方案與方案三類似,只是作了viewport縮放,能百分比還原設計稿。
適配中要解決的問題 :
移動端適配最主要的是使在不一樣屏幕下不用縮放頁面就能正常顯示整個頁面。以上方案都完成了這一需求。其次有幾個需求:
一、解決高清屏下1px的問題,其實有不少hack方法,這裏只講了縮放視口。先將佈局視口設置爲高清屏的物理像素。這樣css中1px就是1個物理像素,這樣看到的線條纔是真正的1px。可是此時視口寬度大於設備的寬度,就會出現滾動條。故對視口進行縮放,使視口寬度縮放到設備寬度。
淘寶團隊在處理安卓端的縮放存在不少問題,因此dpr都作1處理,因此安卓端就沒有解決1px的問題。
二、在大屏手機中一行看到的段落文字應該比小屏手機的多。
因爲淘寶和網易新聞rem都是百分比,故若是用rem一行顯示的文字個數應該是相同的。故對於段落文本不能用rem做爲單位,應該用px處理,對於不一樣的dpr下設置不一樣的字體。
.selector { color: red; font-size: 14px; } [data-dpr="2"] .selector { font-size: 28px; // 14 * 2 } [data-dpr="3"] .selector { font-size: 42px; // 14 * 3 }
對於方案四,無論什麼狀況下,1rem對應的視覺上的寬度都是同樣的,而對應的大屏、小屏手機其視覺寬度固然不一樣,故字體設置爲rem單位時,也能知足大屏手機一行顯示的字體較多這個需求。
五種方案對比:
上面四種方案對設計稿還原程度是有差異的。
除了方案一和方案四之外,其餘方案都是百分比還原設計稿,大屏下元素的尺寸就大。
方案一還原設計稿程度較低,這裏不作說明。
方案二作了百分比適配,部分1px適配,沒有字體適配。
方案三作了百分比適配,沒有1px適配,有字體大小適配。
方案四沒有作百分百適配,佈局要用百分百和flex佈局,作了1px的適配,而且對於段落文字直接能夠用rem作單位,不須要作適配。
方案五作了百分比適配,有1px適配,有字體大小適配。
項目中遇到的問題:
在咱們項目中方案四和方案五都用過。
方案五在使用中沒有遇到什麼問題,就是剛開始沒有作字體適配都是用的rem,後面加入了字體適配,這種方案設計師相對輕鬆些,不用考慮在大小屏幕下的佈局效果。
方案四時沒有跟ui設計師溝通清楚,致使設計師在設計圖上一行排了不少交互元素,在小屏下放不下去,又不能簡單放百分比(元素裏的文字放不下)。因此仍是要作動態判斷大小屏,作出相應適配。這個方案可能設計師須要考慮的多些,儘可能減小一行內的交互元素,當一行交互元素多時要考慮小屏手機怎麼適配。
其實對於1px的適配在蘋果端很好,在android端各個廠商手機差異太大,適配有不少問題。這是爲何絕大多數方案裏都放棄了android端1px適配。不過最近看到不少網站都用了densitydpi=device-dpi這個安卓的私有屬性來兼容部分安卓機型,這個屬性在新的webkit已經被移除了,使用它主要爲了兼容低版本的android系統。
這裏大漠老師針對flexible方案進行了改版,兼容了更多的android機型的1px效果。文章傳送門
他給了個壓縮版的方案,我看了下源碼,把它寫了一遍,不知道有沒有問題,效果是同樣的。
var dpr, scale, timer, rem; var style = document.createElement('style'); dpr = window.devicePixelRatio || 1; scale = 1 / dpr; document.documentElement.setAttribute('data-dpr', dpr); var metaEl = document.createElement('meta'); metaEl.setAttribute('name', 'viewport'); metaEl.setAttribute('content', 'target-densitydpi=device-dpi, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no'); document.documentElement.firstElementChild.appendChild(metaEl); document.documentElement.firstElementChild.appendChild(style); if (980 === document.documentElement.clientWidth) { metaEl.setAttribute('content', 'target-densitydpi=device-dpi,width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1'); } function refreshRem () { var c = '}'; var width = document.documentElement.clientWidth; var isPhone = window.navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i); if (!isPhone && width > 1024) { width = 640; c = 'max-width:' + width + 'px;margin-right:auto!important;margin-left:auto!important;}'; } window.rem = rem = width / 16; style.innerHTML = 'html{font-size:' + rem + 'px!important;}body{font-size:' + parseInt(12 * (width / 320)) + 'px;' + c;; } refreshRem(); window.addEventListener('resize', function () { clearTimeout(timer); timer = setTimeout(refreshRem, 300); }, false); window.addEventListener('pageshow', function (e) { if (e.persisted) { clearTimeout(timer); timer = setTimeout(refreshRem, 300); } }, false);
這些方案只是針對絕大部分機型,項目中可能有些特殊機型有特殊問題,須要特殊對待。好比在這篇文章中做者使用flexible在小米max和榮耀8中有問題,須要特殊hack。傳送門,我沒有這種手機,也沒有對此作驗證。
對於上面的五種方案,方案五看似是適配最好的,可是當項目中引入第三方插件時可能要一一適配,好比:引入一個富文本,裏面設置字體大小的通常都是px,你須要將其一一轉換成rem。而對於方案二,能夠直接用px作單位來百分百還原設計稿,引入的插件時也不用適配。因此說,具體項目中用哪一個方案,其實不光是前端的選擇,還要跟設計師討論下,知足設計需求,選擇最適合項目的方案。
以上是我的對於移動端適配的一些理解,若有不對歡迎指正。
參考文章: