淺談移動前端適配

segmentfault發表於2018-04-11

 1. 什麼是前端適配

  從UI展現層面上:

  我們期望不同尺寸的裝置,頁面可以自適應的展示或者進行等比縮放,從而在不同的尺寸的裝置下看起來協調或者差不多。

  從程式碼實現層面上:

  我們希望前端適配可以用用盡可能簡潔的程式碼來實現。最好一套程式碼實現相容所有裝置,而不是對每個或每種裝置都寫一套方案,不是次次都選用最無奈的方案(Android和iOS分開編寫)。

 2. 關鍵字

  如果你瞭解這些關鍵字,那麼這段大可以跳過,如果後面遇到了問題,感覺有些疑惑,也可以再回來查閱。

  2.1 Viewport/視口

  通俗的講,移動裝置上的viewport就是裝置的螢幕上能用來顯示我們的網頁的那一塊區域[1],但不一定是我們可見的區域。具體來說,分為以下三種。

  2.1.1 Visual Viewport

Visual Viewport: 可見視口。就是移動裝置上可以被我們看見的部分。寬度在移動端通過window.innerWidth獲得(僅限移動端,PC上哪怕是chrome模擬也會有不同的結果)。

  2.2.2 Layout Viewport

Layout Viewport: 佈局視口。

  如果把PC上的頁面放到移動端,以iphone8為例,如果只展示為可見視口的寬度(375px),那麼頁面會被壓縮的特別窄而顯示錯亂,所以移動瀏覽器就決定預設情況下把viewport設為一個較寬的值,比如980px,這樣的話即使是那些為桌面設計的網站也能在移動瀏覽器上正常顯示了。[1]

  而事實上,我們一般看不到如上圖這樣出現橫向滾動條的介面;在手機上訪問頁面時,往往是下圖的樣子:

  這是由於頁面body寬度設定了100%而沒有指定一個具體的寬度導致的,從而使頁面被等比縮放了。由於使用者可以縮放,所以還算能正常瀏覽。

  2.2.3 Ideal Viewport

Ideal Viewport:理想視口,其實就是裝置的可見區域,和可見視口一致。

  設定Ideal Viewport的好處是,只要按照Ideal Viewport來設計樣式稿,使用者就不用能最完美的檢視網站的內容——既不用左右滑動,也不用放大縮小。

  設定理想視口:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

  這段程式碼的意思是將佈局視口的寬度設定為裝置寬度,初始縮放比例為1,最大縮放比例為1,使用者不能縮放。

  2.2 畫素

  2.2.1 物理畫素

物理畫素:一個物理畫素是顯示器(手機螢幕)上最小的物理顯示單元,在作業系統的排程下,每一個裝置畫素都有自己的顏色值和亮度值。[2]

  2.2.2 裝置獨立畫素

裝置獨立畫素:又稱為CSS畫素,就是我們日常程式碼中使用的畫素。瀏覽器內的一切長度都是以CSS畫素為單位的,CSS畫素的單位是px。

  2.2.3 裝置畫素比

  裝置畫素比(簡稱dpr)定義了物理畫素和裝置獨立畫素的對應關係。比如說對於iOS的retina屏,一個裝置獨立畫素就對應著4個物理畫素。這樣的設計可以使畫面更加清晰銳利,如下圖:

 3. 業界的解決方案

  OK,LongLongAgo的字首之後,終於到了正題。回到我們最開始的初心:我們只是想要通過一套程式碼,實現一個可以在不同頁面尺寸上展示差不多的頁面。在這一塊,現在主要有三種方案。

  3.1 Rem的解決方案

  DPR一致時,px在不同的螢幕尺寸上會展示為定寬,這就導致我們的頁面可能會出現滾動條或者佔不滿的情況。而通過rem來設定div的寬高,可以保證頁面可以通過調整html的font-size來整體放大或者縮小,從而達到不管螢幕寬度是多少,頁面都能完美展示的效果。

  例如,針對750*1334的設計稿:

<meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
<script>
    document.documentElement.style.fontSize = window.innerWidth / 7.5 + 'px';
</script>

  這樣,所有的裝置的寬度都是7.5rem,只需要把設計稿上的px值統一除以100,就可以得到相應的rem值了。

  網易也採用的這種方法。

  3.2 Flexible.js

  Flexible是阿里團隊開發的前端適配方案,也是用的rem的方法。那麼第一種方法其實已經能解決前端適配問題了,為什麼阿里還要開發一個Flexible呢?

  在第一種方法中,dpr=1時沒有任何問題,但是在dpr=2或者更高的手機螢幕上,因為物理畫素的增加,存在小於1px的顯示空間。如果採用第一種方法,因為它統一對scale設定為1,那麼我們假如想要實現0.5px, 就只能通過transform的方式。如果有多個這樣的樣式,程式碼就會變得很麻煩。 

.scale{
    position: relative;
}
.scale:after{
    content:"";
    position: absolute;
    bottom:0px;
    left:0px;
    right:0px;
    border-bottom:1px solid #ddd;
    -webkit-transform:scaleY(.5);
    -webkit-transform-origin:0 0;
}

  因此,阿里的flexible方案充分考慮了這種情況,動態的設定了fontsize和scale, 從而使得CSS中的1px等於物理畫素中的1px,在IOS下得到最清晰的體驗。

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;
}

最終在iphone8下頁面的header被設定為:
<meta name="viewport" content="initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">

  具體的大家可以看《使用Flexible實現手淘H5頁面的終端適配

  另外需要指出的一點是:Flexible將頁面分成了100份,頁面的寬度是10rem,對於750的設計稿,我們需要用相應的px數除以75來得到。手動計算是愚蠢的,不同的編譯器都可以下載pix2rem外掛(可以直接寫px然後自動轉換為相應的rem值),直接使用sass或者postcss打包也能達到同樣的功能。

  總結一下上面兩種rem方法,主要思想為:

  • 根據dpr的值來修改html的font-size,從而使用rem實現等比縮放
  • 根據dpr的值來修改viewport實現1px的線

  但是Flexible也有它的侷限性,具體表現為:

  • 不能與響應式佈局相容
  • 對Android沒有做處理,導致1px和backgroudImage還要額外做處理的問題[4]

  所以我們有了第三種解決方案——vw。

  3.3 vw

  vw是基於Viewport視窗的長度單位,在CSS3中和Viewport相關的單位有四個,分別為vw、vh、vmin和vmax。

  • vw: 是Viewport's width的簡寫,1vw等於window.innerWidth的1%
  • vh:和vw類似,是Viewport's height的簡寫,1vh等於window.innerHeihgt的1%
  • vmin: vmin的值是當前vw和vh中較小的值
  • vmax: vmax的值是當前vw和vh中較大的值

  其實vw的方案的寫法和flexible方案的寫法一致——因為flexible其實就是用hack的手段模擬了vw的實現而已。

  具體寫法:針對750px的設計稿,將相應的px值除以75就是vw的值。

  因為此方法不會改變可見視口的寬度,所以可以和media query通用了,另外,也支援了Android上高解析度屏的展示。

  儘管在某些Android機型上還存在相容問題,我們也可以使用Viewport Units Buggyfill,具體見《如何在Vue專案中使用vw實現移動端適配

 總結

  正如大漠所說,flexible模擬vw的時代已經過去,真正的酋長vw已經歸來。

相關文章