理解flexible.js所需的viewport知識

2018.3.1更:

有贊·微商城(base杭州)部門招前端啦,最近的前端hc有十多個,跪求大佬扔簡歷,我直接進行內推實時反饋進度,有興趣的郵件 lvdada#youzan.com,或直接微信勾搭我 wsldd225 瞭解跟多css

有贊開源組件庫·zanUIhtml


viewport探索 flexible.js解讀

http://www.quirksmode.org/mob...
http://www.quirksmode.org/mob...
http://www.quirksmode.org/mob...
http://www.w3cplus.com/mobile...前端

這在這篇文章介紹了viewport的三種視口 visual layout ideal、以及經過此三視口分析了淘寶的flexible.js方案的實現原理。

現代瀏覽器中實現縮放的方式,無怪乎都是「拉伸」像素。html5

在屏幕上,首先介紹的兩個概念。「css像素」、「設備像素」android

css像素與寫在樣式表中定義的寬的度量單位是一致的。ios

能夠這麼理解,css像素和設備像素 是容納的關係。在縮放倍數是200%的狀況下,1個css像素容納了4個設備像素。。
Alt textAlt textAlt textweb

縮放的做用就是改變一個css像素能夠容納設備像素的多少。瀏覽器

PC端

在正常狀況下,一css像素等於一設備像素。放大到200%的狀況下,一個css像素等於四個設備像素。(寬2倍 高2倍)微信

window.innerWidth

屏幕、頁面有不少屬性。app

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

而這些屬性的值的單位就是像素pixels。區別就是其中一些屬性值的度量單位是「設備pixels」而大部分是「css pixels」

window.innerWidth度量的是瀏覽器窗口的寬度。度量單位是css pixels。
Alt text
window.innerWidth的例子。

Alt text

這是縮放100%的狀況。header的寬1220px ,幾乎沾滿了瀏覽器屏幕寬度,window.innerWidth的值爲1231px,根據這個現狀很容易證實window.innerWidth的度量單位是css pixels。

如今將這個頁面放大到200%。
Alt text

咱們發現如今的window.innerWidth變爲了剛纔的一半。就是由於這個屬性的度量單位是css 像素(包含多少css像素),衡量css像素的直觀大小,跟頁面中的dom的css大小比較就ok。

看咱們的頁面,header仍是1220px沒有變。可是在瀏覽器中展現出來的卻只有剛纔的一半。由直觀上的判斷,目前瀏覽器窗口範圍內的css寬的值爲以前的一半。而這個值 就是當前window.innerWidth的值的大小。

一個dom元素的css在數值上大小不變,而設備的像素大小是物理值,也不變,因此也符合以前提到的。縮放實際改變的是一個css像素容納的設備像素的多少。

viewport,用來約束網站中最頂級包含塊元素<html>

默認「body」的寬度取自「HTML」,而「HTML」的寬度取自「viewport」,「viewport」的寬度正好等於「瀏覽器」窗口的寬度。

能夠把「viewport」當成比「html」更高一層的元素,無論咱們有沒有給「html」設置寬,document.documentElement.clientWidth取到的都是「viewport」的寬。而window.innerWidth能取到「瀏覽器」窗口的寬。在PC端這二者的區別僅僅區分在window.innerWidth包含滾動條的寬。
Alt text

這個定義有一個問題出現,紅色背景設爲100%寬,內元素設置min-width980px,當屏幕縮小到980如下時,會出現橫向滾動條,但把滾動條右移就會發現背景缺失了。這就是由於100%的寬其實最大就是瀏覽器窗口的寬。當瀏覽器窗口小於980px時,紅色背景也會縮小到小於980px,因此右劃會出現空白。
Alt text

offsetWidth

document.documentElement.offsetWidth這個屬性取得是html的寬度。(IE中度量的是viewport)

若是將html當作一個塊級元素來說,html的寬默認爲父元素的100%,高度默認被子元素撐開。
因此默認狀況下,offsetWidth取得值是繼承於viewport的寬,而viewport的寬等於瀏覽器窗口的寬。
而offsetHeight的值默認由子元素的高決定。
顯式書寫了html的height或者width的時候,offsetHeight/width取的就是顯式設置的值。

還有個狀況就是設置html爲100%的時候,實際上這個意思就是將html的高設置爲viewport的高的100%,同理,viewport的高等於瀏覽器窗口的高。能夠用window.innerHeight取得。

Alt text

pc端小結

  1. window.innerWidth的度量單位是「css像素」,表象的概念就是當前瀏覽器窗口包含了多少css像素大小的dom。
  2. 默認「body」的寬度取自「HTML」,而「HTML」的寬度取自「viewport」,「viewport」的寬度正好等於「瀏覽器」窗口的寬度。

移動端

  • layout viewport
  • visual viewport

layout viewport和visual viewport

layout viewport的概念其實跟pc端的viewport是同樣的,是做爲html的」上層「元素。將寬繼承給html。html內的各元素都是以layout viewport爲基準進行佈局的。但跟pc端不一樣的是,pc端的viewport的寬是由瀏覽器的窗口的寬決定的,用戶能夠手動拖動窗口改變寬的大小。可是移動端的不一樣平臺的瀏覽器呈現不一樣的layout viewport。

ios980px,android800px。

將visual viewport想象成覆蓋手機屏幕的一個框,這個框帶有相似pc端縮放的功能,並且這個框的度量單位也是css像素。這就意味着,在layout viewport不變的狀況,咱們能看到多少css像素的東西,取決於這個框的縮放程度。默認狀況下。大多數移動端瀏覽器會將visual viewport這個框縮放到與layout viewport相同。

拿ios設備舉例,layout viewport固定爲980px,默認打開頁面的狀況下,visual viewport會將這個框縮放到980px。這樣咱們就能看到所有的內容了。

Alt textAlt text

默認狀況下html元素的寬取自layout viewport,那麼不一樣機型瀏覽器的layout是不一樣的,ios980px,android800px。

因此在不設置任何條件的狀況下,一個寬度是1220px的div在ios模擬器下是這樣的。

Alt text

html的寬繼承自layout viewport,980px。多出的240px在layout viewport以外,須要經過滑動屏幕才能見到。

在pc端咱們經過document.documentElement.clientWidth取得viewport的寬度。

可是移動端有layout和visual兩個viewport,該怎麼取值?

以前講到了layout viewport的概念跟pc端的viewport相近,都是來約束html元素的寬的。即html元素是以layout viewport做爲參考系進行佈局的。因此這裏的document.documentElement.clientWidth能獲取到layout viewport的寬度尺寸。

在pc端是經過window.innerWidth來度量瀏覽器窗口的寬的,而在移動端,以前講到的visual viewport模擬的那個框,就至關於瀏覽器的窗口。因此在移動端能夠經過window.innerWidth來取得visual viewport的寬。其值會根據縮放的程度而改變。讀到的值爲當前屏幕上x方向的css像素的值。

在pc端咱們總結過window.innerWidthdocument.documentElement.clientWidth在縮放不一樣的狀況下只是相差一個滾動條的寬度。可是在移動端,window.innerWidth(visual viewport)document.documentElement.clientWidth(layout viewport)在縮放的狀況下值是不一樣的。緣由在於移動端的document.documentElement.clientWidth(layout viewport)老是固定的。

viewport meta標籤

width:控制 layout viewport 的大小。
height:和 width 相對應,指定高度。
initial-scale:初始縮放比例,也便是當頁面第一次 load 的時候縮放比例。
maximum-scale:容許用戶縮放到的最大比例。
minimum-scale:容許用戶縮放到的最小比例。
user-scalable:用戶是否能夠手動縮放

width 設置layout viewport的寬度

爲何要設置這個屬性?

Alt text

咱們以前提到過在原始的頁面上visual viewport會自動將視口縮放到與layout viewport同寬,這樣就能看到所有的內容,用戶天然會放大頁面,可是遇到較長的文字段落須要將屏幕左右滑動才能閱讀徹底。
在沒有viewport meta標籤以前能夠這樣優化。

Alt text

將html的寬度設置爲375px,這樣文字就能在一個頁面內顯示徹底,放大的時候不須要左右滑動了。

可是在初始化的時候內容過小,不易於閱讀。

因而蘋果爲了解決這個問題提出了viewport meta。

目的之一就是能夠手動的設置layout viewport的值。

既然在移動端用不了這麼大的像素寬度,那乾脆就把layout viewport的值減少,visual viewport也會自動縮放在屏幕上顯示全部的layout viewport的內容。

可是咱們以前也講到了layout viewport的寬是限制HTML元素的寬度的。因此html元素內的元素的寬須要設置不大於layout viewport的寬度值,才能保證徹底顯示在visual viewport內。

Alt text
Alt text
Alt text

此時對於viewport meta標籤裏只設置了width=300字段,依靠縮放的現象,visual viewport 和 layout viewport的寬度一致

根據這個現象咱們只要根據設計稿中規劃好的寬度,進行viewport width的寬度相同的設置就能夠了。依靠縮放原理就能夠在所有機型上呈現同樣的寬度。(高度是根據寬度自適應的)

可是目前的viewport只設置了width,如今的頁面用戶仍是能夠進行手動縮放的,爲了禁止手動縮放,須要增長一個字段

<meta name="viewport" content="width=375" user-scalable=no>

可是這個user-scalable=no字段給部分安卓原生瀏覽器及部分安卓webview的自動縮放功能帶來了限制。
本例中設置的layout viewport width爲375px。

Alt text

原本應該在屏幕上正好填充內容的,如今出現了左右滾動條。
這個現象的緣由就是layout viewport的寬度由viewport meta的width值設置成了375px,可是visual viewport的值只有360px,因此屏幕上只能顯示360px的內容,剩下的須要左右滑動。

至於這裏爲何是360px,而不是其餘的值。
來看看ideal viewport的概念。

ideal viewport

最開始提到了visual viewport 和 layout viewport的概念。這兩個值都是跟頁面實際的大小有關的,可是這個ideal viewport,用最直觀的話來說,就是每一種機型所對應的屏幕尺寸,固然也是css像素來度量的。

Alt text
Alt text

拿Note2舉例,就是剛剛gif裏展現的那臺機子,ideal viewport的值就是360px,而這個值與縮放值和visual viewport是息息相關的。是做爲一個基準值。 (「ideal viewport」「縮放值」「visual viewport」三者息息相關)

visual viewport width = ideal viewport width / zoom factor

這個zoom factor縮放值以前也講到過,在大部分狀況下是自動計算的。拿note2舉例,layout viewport的寬設置了375px(沒有設置user-scalable=no),當前的ideal viewport的寬爲360px。

<meta name="viewport" content="width=375">

頁面自動縮放visual viewport會計算得爲375。因此此時在屏幕的visual viewport能看到全部的layout viewport的寬度的內容。

Alt text

一旦設置了

<meta name="viewport" content="width=375, user-scalable=no">

以後,在部分安卓原生瀏覽器以及安卓webview上,縮放值就不會自動計算了,而是取固定值1。
根據上述的計算公式,
visual viewport width = 360 / 1 = 360px
即這個時候頁面呈現以下
Alt text

既然縮放值不會自動縮放,那麼能夠經過js去動態設置縮放值,看看能不能修復這個問題。

再來看viewport meta的一個屬性:initial-scale 這個值能夠顯式地設置縮放值。記着,根據上述的公式,縮放值是相對ideal viewport width進行縮放的。

一個特殊的width的值width=device-width。設置這個值,瀏覽器會將當前頁面的layout viewport的寬度設置爲設備的ideal viewport的寬度。而layout viewport的值咱們能夠經過document.documentElement.clientWidth取得。

設置initial-scale指令實際上作了兩件事:

  1. 把頁面的初始縮放因素設置了一個有意義的值,是相對於ideal viewport進行計算的,產生了visual viewport的寬度。
  2. 根據剛剛計算獲得的visual viewport的寬度值去設置layout viewport 的寬度值。

有了這些概念,開始咱們的測試。

<meta name="viewport" content="width=device-width, initial-scale = 1, user-scalable=no" />// 目的是講當前頁面的layout viewport設置成ideal viewport width
var idaelViewport = document.documentElement.clientWidth; // 取得當前頁面的ideal viewport width
var visualViewport = 375;
alert(idaelViewport)
var zoomView = idaelViewport/visualViewport;  // 動態計算縮放值
$('.j_Viewport').attr('content', 'user-scalable=no, initial-scale='+zoomView ); // 動態設置viewport meta
alert(zoomView)

問題是在某些安卓下initial-scale在設置成1的狀況下才能經過計算設置layout viewport 和 visual viewport的值,所在在這個方案裏這個也是失敗的。

基於上述的知識點,再來看淘寶出品flexible.js的實現原理就很簡單了。

flexible.js一共作了這麼幾件事,咱們拿iphone5舉例子,
iphone5的ideal viewport width爲320。

  1. 根據設備定義dpr
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;

將iphone的不一樣型號劃分爲一、二、3
將安卓的dpr統一設爲1
iphone5環境下,此時dpr=2,scale=0.5

  1. 給html設置一個data-dpr屬性
docEl.setAttribute('data-dpr', dpr);
  1. 動態設置viewport meta
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);
 }

Alt text

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

如今在iphone5的環境,單獨設置initial-scale=0.5有兩個做用,以前也提到。根據公式

visual viewport width = ideal viewport width / zoom factor

能夠獲得visual viewport width=320/0.5 = 640
就是講此時頁面的visual viewport width和layout viewport width的值都設置爲640px。

同時將html的寬也設置成640px。
Alt text

如今這個庫已經把當前設備上的html寬度設置好了,不能縮放,visual viewport width和layout viewport width的值也相同,徹底展現。

接下來就是要處理頁面中的元素的大小了。

  1. 利用rem設置頁面元素大小
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;
}

這段代碼中的docEl.getBoundingClientRect().width;取得的就是html的寬,也就是當前頁面的最大寬。
var rem = width / 10;
而後將寬度/10獲得一個基準數,在iphone下,這個值就rem=640/10 = 64。
docEl.style.fontSize = rem + 'px';將hmtl元素的fontSize設置爲64px。

rem的工做方式就是相對於html元素的fontSize值進行計算的。

這個庫的功能大概已經完成了,幾段邏輯最後的目的就是將hmtl的fontSize值設置成64px,這該如何工做?

Alt text

咱們通常拿到的設計稿都是750px的,默認將設計稿分紅10份,將75px的像素對應一個rem,量的多少的像素相應的作一下換算,若量的的一張圖片爲30px,則對應的rem爲30/75=0.4rem。而後在對應的頁面裏定義這個圖片的寬就爲0.4rem。

這就是flexible.js的工做方式。

原創文章,轉載請註明出處。

對我以上的理解有疑問和意見的歡迎找我私聊~微博-寫前端的暹羅

相關文章
相關標籤/搜索