移動端適配知識你到底知多少

本文從設備像素,CSS像素這些基本概念出發,先向你們介紹移動端適配必需要掌握的一些基本像素知識及viewport理論,最後在向你們介紹移動端適配解決方案:flexible.js和vw/vhhtml

設備像素 & CSS像素 & 設備獨立像素

設備像素:device pixel,dp,物理像素,不可變,下圖標紅的部分指的就是設備像素前端

CSS像素:web編程用到的,咱們在JS和CSS中使用的10px就是CSS像素,是可變的。CSS像素受屏幕縮放和設備像素比(dpr)的影響。如咱們網頁的中的字體在網頁放大以後會變大,還有在移動端看起來會比PC端小一些android

設備獨立像素:device independent pixel,dip,與設備無關的像素web

再來聊下它們之間的關係:PC端和移動端編程

PC

100%縮放瀏覽器

1設備獨立像素 = 1設備像素bash

200%縮放app

1設備獨立像素 = 2設備像素iphone

移動端

由於不一樣設備的PPI不一樣,在標準屏幕下(160PPI)ide

1設備獨立像素 = 1設備像素

至於CSS像素和他們之間的關係,等下面講到了設備像素比再說

設備像素比

device pixel ratio,dpr

DPR = 設備像素 / 設備獨立像素

下圖是一些手機的設備像素比數據

在JS中咱們能夠經過window.devicePixelRatio來獲得當前設備的dpr
在CSS中咱們能夠經過-webkit-device-pixel-ratio來進行媒體查詢

注意,當咱們放大或者縮小屏幕的時候,window.devicePixelRatio是可變的

有了dpr再來講說,CSS像素和設備像素之間的關係

當dpr爲1,1個CSS像素對應1個設備像素
當dpr爲2,1個CSS像素對應4個設備像素
當dpr爲3,1個CSS像素對應9個設備像素
......

針對有些同窗後面提出的疑問,我詳細解釋這塊,當dpr爲1,此時1個CSS像素對應1個設備像素,這個仍是很好理解的,當dpr爲2,此時在水平方向上的設備像素是dpr爲1的兩倍,豎直方向上的設備像素也是dpr爲1的兩倍,因此此時的1個CSS像素對應2^2個設備像素,這個就至關於咱們把一個矩形的長寬放大爲以前的兩倍,此時的矩形面積爲以前的四倍,當dpr爲3時,此時的1個CSS像素對應3^2個設備像素,因此1個CSS像素對應dpr^2個設備像素

下圖生動的表示了他們之間的關係

這裏跟你們說一個小技巧,就是在移動端的時候能夠根據dpr的值,使用不一樣分辨率的圖片,如2X仍是3X,這樣能夠保證與在普通屏幕上看到的圖片效果一致,不至於失真

DPI & PPI

DPI(Dots Per Inch)源於印刷行業,表示每英寸打印機噴的墨汁點數
PPI(Pixels Per Inch)計算機借鑑了DPI,創造了PPI,表示每英寸的像素數量,即像素密度

PPI的計算公式以下:

如今兩者均可用於描述計算機顯示設備的像素密度,意思同樣

下面一張圖描述了iphone三個機型的參數

其中能夠發現iphoneX的像素密度達到了458ppi,完爆其它兩個

Retina

Retina屏幕即視網膜屏幕,是蘋果發佈iphone 4提出的。之因此稱做是視網膜屏幕,是由於ppi過高,人類沒法分辨出屏幕上的像素點,目前不少智能手機都採用Retina屏幕

iphone3G/S 和 iphone4的屏幕尺寸都是3.5寸,可是iphone4在水平和豎直方向的物理像素都是iphone3G/S的一倍

PC端幾個尺寸

PC端有幾個尺寸咱們須要弄懂下,它們是:

  • screen.width
  • window.innerWidth
  • document.documentElement.clientWidth
  • document.documentElement.offsetWidth
  • ...

screen.width

screen.width指的是咱們顯示器的水平方向的像素時,不隨着咱們瀏覽器窗口的變化而變化,是用設備像素衡量的

window.innerWidth

window.innerWidth指的是瀏覽器窗口的寬度,是能夠變化的,因此使用的是CSS像素

下面是100%縮放,window.innerWidth的截圖

能夠發如今100%縮放狀況下,window.innerWidth的值爲1192,window.innerHeight的值爲455,接着咱們嘗試將放大到200%,再來看看效果

能夠看到當放大2倍以後,window.innerWidth和window.innerHeight都變成了放大以前的1/2,可是此時window.devicePixelRatio變成了放大以前的2倍,爲何是這樣子呢?

其實這個也不難理解?由於window.innerWidth是用CSS像素衡量的,放大兩倍以後,瀏覽器窗口只能看到以前一半的內容,因此window.innerWidth是以前的一半,而dpr = 設備像素 / 設備獨立像素,這裏的設備獨立像素就是咱們的window.innerWidth,因此dpr變爲原來的2倍,若是看的有點暈,不如嘗試縮放本身的瀏覽器看下效果就知道了

document.documentElement.clientWidth

document.documentElement.clientWidth指的是viewport的寬度,與window.innerWidth的區別就只差了一個滾動條

document.documentElement.offsetWidth則是取得html標籤的寬度

看到沒document.documentElement.offsetHeight此時爲0,我打開調試定位了下,發現此時html高度確實是爲0,而document.documentElement.clientHeight此時爲455,是viewport的高度,只不過此時viewport的高度和window.innerHeight相等

小結

對於pc端,總之記住如下幾點:

  • window.innerWidth指的是瀏覽器窗口的寬度(包含滾動條),用CSS像素衡量
  • document.documentElement.clientWidth指的是viewport的寬度,等於瀏覽器窗口的寬度(不包含滾動條)
  • document.documentElement.offsetWidth指的是html的寬度,默認爲瀏覽器窗口的寬度
  • document.documentElement.offsetHeight指的是html的高度,沒有顯示給html指定高度的話,爲0

移動端的三個viewport理論

移動端的話和PC端大相徑庭,咱們必須先要掌握三個viewport:

  • layout viewport
  • visual viewport
  • ideal viewport

layout viewport

佈局layout,和PC端的viewport很像,PC端的viewport的寬由瀏覽器窗口的寬決定的,用戶能夠經過拖動窗口或者縮放改變viewport的大小,可是在移動端則不一樣,在IOS中 layout viewport默認大小980px,在android中layout viewport爲800px,很明顯這兩個值都大於咱們瀏覽器的可視區域寬度。咱們能夠經過document.documentElement.clientWidth來獲取layout viewport的寬度

visual viewport

有了layout viewport,咱們還須要一個viewport來表示咱們瀏覽器可視區域的大小,這個就是visual viewport。visual viewport的寬度能夠經過window.innerWidth獲取

移動端瀏覽器爲了避免讓用戶經過縮放和滑動就能看到整個網頁的內容,默認狀況下會將visual viewport進行縮放到layout viewport同樣大小,這也就解釋了爲何PC端設計的網頁在手機上瀏覽會縮小,其實這是跟移動瀏覽器默認的行爲有關係

ideal viewport

設備理想viewport,有如下幾個要求:

  • 用戶不須要縮放和滾動條就能查看全部內容
  • 文字大小合適,不會由於在高分辨率手機下就顯示太小而看不清,圖片也同樣

這個viewport就叫作ideal viewport。可是不一樣的設備的ideal viewport不同,有320px,有360px的,還有384px的......

總之在移動端佈局中咱們須要的是ideal viewport。它等於咱們移動設備的屏幕寬度,這樣針對ideal viewport設計的網站,在不一樣分辨率的屏幕下,不須要縮放,也不須要用戶滾動,就能夠完美呈現

meta標籤

咱們能夠經過meta標籤設置咱們viewport,下面這段代碼你應該見過不止一次了

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
複製代碼

這段代碼的做用就是設置當前的layout viewport的寬度爲設備的寬度,初始化縮放爲1.0,同時不容許縮放,這應該是咱們你們都想要的效果

關於meta viewport標籤最初是由Apple公司在Safari瀏覽器中引入的,目的就是爲了解決移動設備的viewport的問題,後來其他公司紛紛效仿,它有如下幾個屬性:

屬性 說明
width 設置layout viewport的寬度,數字或者device-width
height 設置layout viewport的高度,數字或者device-height
initial-scale 頁面初始縮放值
maximum-scale 用戶最大縮放值
minimum-scale 用戶最小縮放值
user-scalable 容許用戶縮放,yes或no

width=device-width

設置當前的layout viewport的寬度爲設備的屏幕寬度,這樣咱們的網站就是針對設備的屏幕寬度進行排版的,而這個不正是上面所說的ideal viewport,因此經過這樣咱們可讓咱們layout viewport的寬度等於ideal viewport的寬度

可是在iphone和ipad上,不管是橫屏仍是豎屏,device-width都是豎屏的屏幕寬度

下面是我設置了width=device-width以後

能夠看到設置了width=device-width以後,document.documentElement.clientWidth和window.innerWidth都變成了375,即設備的屏幕寬度

initial-scale=1.0

經過這種方式咱們也可讓咱們的layout viewport的寬度等於ideal viewport的寬度,緣由是initial-scale是針對ideal viewport進行縮放的,當咱們設置爲1.0也就是縮放100%,就可讓咱們的layout viewport和ideal viewport同樣大

下面是我設置了initdial-scale=1.0以後

效果同設置了width=device-width同樣

但此次咱們發如今winphone上,不管橫屏仍是豎屏,都將layout viewport的寬度設置爲豎屏的屏幕寬度

因此爲了兼容,建議咱們同時寫上這兩個屬性,即

width=device-width,initial-scale=1.0

initial-scale=其它值

當咱們設置init-scale爲其餘值又是個什麼狀況呢?

下圖是我設置了initial-scale=0.5的效果

能夠看到document.documentElement.clientWidth和window.innerWidth都變成了750,爲initial-scale=1的兩倍,由此咱們能夠有一個假設:

layout viewport寬度 = ideal viewport寬度 / initial-scale

咱們繼續設置initial-scale=3,按照上述的結論,此時document.documentElement.clientWidth和window.innerWidth應該爲125

事實證實咱們上述的假設是正確的

flexible.js源碼分析

flexible.js是阿里無線前端團隊開源的用於移動端適配的庫。雖然如今官方都認可能夠放棄這個解決方案了,可是瞭解其中的思想仍是很重要的

因爲viewport單位獲得衆多瀏覽器的兼容,lib-flexible這個過渡方案已經能夠放棄使用,不論是如今的版本仍是之前的版本,都存有必定的問題。建議你們開始使用viewport來替代此方案。vw的兼容方案能夠參閱《如何在Vue項目中使用vw實現移動端適配》一文。

廢話很少說,直接上代碼

(function flexible (window, document) {
  var docEl = document.documentElement
  var dpr = window.devicePixelRatio || 1

  // adjust body font size
  function setBodyFontSize () {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px'
    }
    else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
  }
  setBodyFontSize();

  // set 1rem = viewWidth / 10
  function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
  }

  setRemUnit()

  // reset rem unit on page resize
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function (e) {
    if (e.persisted) {
      setRemUnit()
    }
  })

  // detect 0.5px supports
  if (dpr >= 2) {
    var fakeBody = document.createElement('body')
    var testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) {
      docEl.classList.add('hairlines')
    }
    docEl.removeChild(fakeBody)
  }
}(window, document))
複製代碼

因爲版本不一樣,可能你們拿到的代碼局部地方有所不一樣,可是總體思路仍是不變的。

var docEl = document.documentElement
var dpr = window.devicePixelRatio || 1

// adjust body font size
function setBodyFontSize () {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px'
    }
    else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
}
setBodyFontSize();
複製代碼

setBodyFontSize這個函數的做用就是設置body標籤的fontSize,fontSize的值dpr * 12,這個函數的做用是爲了覆蓋html的fontSize

// set 1rem = viewWidth / 10
function setRemUnit () {
    var rem = docEl.clientWidth / 10
    docEl.style.fontSize = rem + 'px'
}

setRemUnit()
複製代碼

首選獲取document.documentElement.clientWidth的值,這個值表示當前設備layout viewport的寬度(你能夠理解html標籤的寬度),在iphone6 7 8下這個值是750,而後將整個視口分紅10份,這樣每一份的寬度爲clientWidth / 10,即1rem = clientWidth / 10,之因此分紅10份,徹底是方便計算,你也能夠隨意切分,可是最小值不要小於12,由於在谷歌瀏覽器中有最小fontSize的限制

// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageshow', function (e) {
    if (e.persisted) {
      setRemUnit()
    }
})
複製代碼

這段代碼是爲了在window觸發了resize和pageShow事件以後自動調整html的fontSize值

// detect 0.5px supports
if (dpr >= 2) {
    var fakeBody = document.createElement('body')
    var testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) {
      docEl.classList.add('hairlines')
    }
    docEl.removeChild(fakeBody)
}
複製代碼

這句話的代碼是檢測0.5px的支持,可是我本身還沒弄懂,有哪位同窗若是弄明白了,能夠在下面發個評論,你們互相學習

還有一點差點忘記了,設置viewport

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
複製代碼

查看demo

看了下flexible的實現,我本身也簡單的實現了下,基本思想同flexible.js同樣,只不過我添加了一個自動計算scale的功能

var docuEl = document.documentElement
var metaEl = document.createElement('meta')
var dpr = window.devicePixelRatio
scale = 1 / dpr
metaEl.setAttribute('name', 'viewport')
metaEl.setAttribute('content', `initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale},user-scalable=no`)
docuEl.setAttribute('data-dpr', dpr)
document.head.appendChild(metaEl)

function resizeFontSize() {
    docuEl.style.fontSize = docuEl.clientWidth / 10 + 'px'
}
resizeFontSize()

window.onresize = resizeFontSize
window.onpageshow = function(e) {
    if (e.persisted) {
      resizeFontSize()
    }
}
複製代碼

vw & vh

現在flexible.js已經成了過去式,咱們實現移動端自動適配,還要在head中添加js代碼,對於任何一個追求完美的人確實不能忍,還好咱們有vw和vh,並且它們在現在大部分手機中都獲得了支持

查看我可否用vw

未完待續

相關文章
相關標籤/搜索