大概是去年的7月想寫這個內容去加深自己的理解。現在終於回來補上這篇入門小結了。
1.問題描述
適配的目標:在不同尺寸的手機裝置上,頁面“相對性的達到合理的展示(自適應)”或者“保持統一效果的等比縮放(看起來差不多,但不是完全等比例,對於字型我們並不喜歡等比例的去放縮)”。
問題:手機裝置的尺寸不同,讓頁面在不同的手機裝置上顯示的效果看起來大致相同或者展示效果比較合理就成了一個問題。 目前移動端比較通用的幾個方案
- 媒體查詢和rem 適配
- viewport 縮放, rem 佈局,js計算
- vw適配方案(以後可能的方案)
開始使用這幾個方法的時候,在想為啥要這樣做,優缺點是啥咧?
2.關於viewport
知道了通用方法進入程式碼的正題,寫一個如圖簡單的HTML頁面,在Chrome瀏覽器iPhone6模擬器下除錯發現頁面的可視區域寬為980px。
為啥是980px了?預設的。瀏覽器廠商為了讓那些傳統的為桌面瀏覽器設計的網站在小螢幕下也能夠很好顯示,所以把**佈局視口(layout viewport)**寬度設定地很大,一般在768px ~ 1024px之間,最常見的寬度是980px。
那為啥980px佈局寬,能在375px螢幕寬的裝置下完好顯示了? 因為縮小。
Narrow screen devices (e.g. mobiles) render pages in a virtual window or viewport, which is usually wider than the screen, and then shrink the rendered result down so it can all be seen at once. Users can then pan and zoom to see different areas of the page. For example, if a mobile screen has a width of 640px, pages might be rendered with a virtual viewport of 980px, and then it will be shrunk down to fit into the 640px space. This is done because many pages are not mobile optimized, and break (or at least look bad) when rendered at a small viewport width. This virtual viewport is a way to make non-mobile-optimized sites in general look better on narrow screen devices. (Using the viewport meta tag to control layout on mobile browsers)
大概翻譯:窄螢幕裝置(例如移動裝置)在虛擬視窗或視口中渲染頁面,該視窗或視口通常比螢幕寬,然後縮小渲染結果,以便可以立即看到它們。例如,如果移動螢幕的寬度為640px,則可能使用980px的虛擬視口渲染頁面,然後縮小頁面以適應640px空間。 這樣做是因為許多頁面不是移動優化的,並且在以小視口寬度渲染時會中斷(或者至少看起來很糟糕)。 此虛擬視口是一種使非移動優化網站在窄屏裝置上看起來更好的方法。雖然已經很人性化的設計了,但如下圖不通過使用者縮放和橫向滾動滾動條,還是很難看清楚頁面內容的。
2.1ppk的 關於三個viewport的理論
除了上文中大概提到的 layout viewport, virtual viewport,還有ideal viewport。
- layout viewport:佈局視口,在呈現頁面之前,瀏覽器需要知道佈局視口的寬度,如沒有任何進一步的說明(如設定),瀏覽器自己選擇寬度。瀏覽器選擇了佈局視口的尺寸,使其在完全縮小模式下完全覆蓋螢幕。
- virtual viewport:可見視口,可視視口是當前在螢幕上顯示的頁面的部分。使用者可以縮放以更改可視視口的大小。
- ideal viewport:理想的視口,它提供了裝置上理想的網頁大小。因此,理想視口的尺寸因裝置而異。(ideal viewport 的意義在於,無論在何種解析度的螢幕下,針對ideal viewport 而設計的網站,不需要手動縮放和橫向滾動條都可以完美地呈現給使用者)
layout viewport 和 virtual viewport的關係用下文的話來描述再好不過了
想象一下,佈局視口是一個不會改變大小或形狀的大影像。現在你有一個較小的框架,通過它你可以看到大影像。小框架周圍被不透明材料包圍,這些材料遮擋了除大部分影像之外的所有部分的檢視。您可以通過框架看到的大影像部分是可視視口。您可以在保持框架的同時遠離大影像(縮小)以一次檢視整個影像,或者您可以靠近(放大)以僅檢視一部分。您也可以更改框架的方向,但大影像(佈局視口)的大小和形狀永遠不會更改。
這樣看來layout viewport, virtual viewport,對移動端瀏覽器的顯示幫助還是不夠的(瀏覽器廠商設定的一個寬度,通過一定的“自由“放縮顯示在手機裝置上)。現在需要一個基礎的寬度設定,放縮比例是可控的,然後頁面剛好完全顯示在螢幕上(指寬度上)。ppk第三個視口ideal viewport,就出現了。
三篇值得一看的文章
①A tale of two viewports — part one(Concept: device pixels and CSS pixels,這個例子灰常好)
②A tale of two viewports — part two,
ideal viewport 和 virtual viewport 的關係
可視視口寬度=理想視口寬度/縮放係數 縮放係數 =理想視口寬度/可視視口寬度 簡單理解一下,如果理想視口寬度=裝置寬度=375px,然後後縮放係數為0.5,計算出可視視口寬度為750px; 如果此時的佈局視口剛好等於750px;頁面的顯示是不是非常完美了(頁面再也不是“自由“放縮顯示了)複製程式碼
2.2viewport meta tag 的引入
為了更好的控制視口的大小比列,蘋果公司在其safari瀏覽器中引入meta viewport(UsingtheViewport),安卓以及各大瀏覽器廠商也都紛紛引入。下面這個標籤是很多人接觸移動端頁面都會看到的。那麼這個標籤做了什麼了?
①將佈局視口寬度設定為理想的視口寬度width=device-width,
②根據初始縮放係數和理想視口寬度計算出可是視口,
③將佈局視口寬度設定為剛剛計算的可視視口寬度(佈局視口寬度取②,③計算中值大的)。 (現在看來,頁面的初始佈局寬度,以及放縮係數是可控靠譜的了,不容易啊,雖然這個標記被流行的移動瀏覽器支援,但目前還是草案)
<meta name="viewport" content="width=device-width,initial-scale=1">複製程式碼
3.DPR,裝置畫素,備獨立畫素
用如下的標籤歡快的按著設計稿,以px為單位寫著程式碼,然後設計師看了效果圖就跑來了,這個邊框怎麼這麼粗呀?圖示,這個圖示怎麼看起來這麼模糊啊?還有怎麼這個圖示在這個小螢幕手機上面這麼大......
<meta name="viewport" content="width=device-width,initial-scale=1">複製程式碼
3.1一些需要的概念
物理畫素(physical pixel) :一個物理畫素是顯示器(手機螢幕)上最小的物理顯示單元,在作業系統的排程下,每一個物理畫素都有自己的顏色值和亮度值。從螢幕在工廠生產出的那天起,它上面物理畫素點就固定不變了,單位 pt(同裝置畫素)。
裝置獨立畫素:(又稱裝置無關畫素 Device Independent Pixels 、密度獨立性 Density Independent或裝置獨立畫素,簡稱DIP或DP)是一種物理測量單位,基於計算機控制的座標系統和抽象畫素(虛擬畫素),由底層系統的程式使用,轉換為物理畫素的應用。
css畫素: CSS畫素是一個抽像的單位 ,1個 CSS 畫素的大小在不同物理裝置上看上去大小總是差不多。(為了保證瀏覽的一致性)關於一些長度單位的介紹(有關於css畫素描,圖片來源於此)
裝置解析度對畫素單元的影響:1px乘1px的區域被低解析度裝置(例如典型的計算機顯示器)中的單個點覆蓋,而同一區域被16個點覆蓋 在更高解析度的裝置(如印表機)中。在不同的裝置之間,1個CSS畫素所代表的物理畫素是可以變化的。
DPR:裝置畫素比簡稱為dpr,其定義了物理畫素和裝置獨立畫素的對應關係。它的值可以按下面的公式計算得到:
裝置畫素比 = 物理畫素 / 裝置獨立畫素
問題:iphone5 的dpr 是 2 螢幕寬度320px 那麼它的裝置物理畫素寬是多少?(640)複製程式碼
來看看圖兒(內容來源A tale of two viewports — part one)
①CSS畫素與裝置畫素完全重疊。
②CSS畫素拉伸,現在一個CSS畫素與幾個裝置畫素重疊。
③CSS畫素開始縮小,一個裝置畫素現在與幾個CSS畫素重疊。
在同樣一個裝置上,1個CSS畫素所代表的物理畫素是可以變化的;
3.2影像模糊的由來,1px邊框問題
點陣圖:是由畫素(Pixel)組成的,畫素是點陣圖最小的資訊單元,儲存在影像柵格中。
影像問題:理論上,1個點陣圖畫素對應於1個物理畫素,圖片可以完美清晰的展示。一個點陣圖畫素是柵格影像(如:png, jpg, gif等)的最小資料單元。在Retina螢幕下(此時dpr假設為2)200 ×200大小的圖片,樣式大小也設定為width:200px;,heigth:200px;此時1px畫素被4個物理畫素點填充。1個點陣圖畫素對應了4個物理畫素,由於單個點陣圖畫素不可以再進一步分割,所以只能就近取色,從而導致圖片模糊。( 移動端高清、多屏適配方案,此段內容觀點和圖片來源於此)
同理反過來在普通螢幕下(此時dpr假設為1),400 ×400大小的圖片,樣式大小也設定為width:200px;,heigth:200px;(我們習慣說此時用的2倍圖)_。_一個物理畫素點對應4個點陣圖畫素點,所以它的取色也只能通過一定的演算法得到,顯示結果就是一張只有原影像素總數四分之一的圖,(我們稱這個過程叫做downsampling)肉眼看上去圖片不會模糊,但是會覺得圖片缺少一些銳利度,或者是有點色差。
1px 物理畫素邊框問題:在頁面不設定放縮的情況下是很難實現的。
如圖1dpr倍屏,2dpr倍屏,3dpr倍屏,需要實現1px 物理畫素邊框border: 1px;,border: 0.5px;border: 0.33px; 然而有的瀏覽器並不能識別0.5px,0.33px。
4.移動端頁面適配的簡單解決
一些Relative lengths,rem,em vw,vh......此處用到了 rem ,rem的官方定義來一下~~(www.w3.org/TR/css3-val…)相對於根元素(即html元素)font-size計算值的倍數。
舉個例子,如果頁面的html的font-size 設定 為 20px,那麼 1rem= 20px;
再舉個例子:以iPhone6的設計稿為為基礎來計算(因為我家設計師喜歡出iPhone6的稿子)
設 備 | 裝置寬度 | 根元素font-size/px | 螢幕寬 |
---|---|---|---|
iPhone5 | 320 | 17.066(約等於) | 18.75rem |
(baseWidth)iPhone6 | 375 | 20 | 18.75rem |
iPhone6 Plus | 414 | 22.080(約等於) | 18.75rem |
1 //假設螢幕螢幕寬度 等於佈局寬度 等於可視視窗寬度。
2 // iPhone6 (18.75份是隨便取的)
3 以iPhone6為基礎,螢幕寬度為375px,將螢幕寬度分成18.75份,每一份寬度為20px;
4 設定html的font-size 為20px; 1rem = 20px;
5 // iPhone5
6 iPhon5,螢幕寬度為320px,將螢幕寬度分成18.75份,每一份寬度為17.066;
7 設定html的font-size 為17.066; 1rem 約等於 17.066;
8
9 以iphonp6的設計稿某div的高為20xp 寬為20px 寫了一個樣式
10 .haha {
11 width: 1rem; /* iphonp6 下顯示為20px */;
12 height: 1rem; /* iphonp6 下顯示為20px */
13 }
14 //上述那段css在iPhone5下 表達的寬高是多少了
15 .haha {
16 width: 1rem; /* (320/18.75)px*/;
17 height: 1rem; /* (320/18.75)px*/
18 }
19 // 看一組數字 320/375 = (320/18.75)/20 螢幕寬度比,等於設計稿圖片放縮比。
20 // 這樣設計稿就成比例在不同寬度手機螢幕上面顯示了複製程式碼
根元素fontSize公式:width/fontSize = baseWidth/baseFontSize
4.1媒體查詢和rem 適配
先參考一下 比如微博和京東,咦~~ 用的是媒體查詢設定。
@media only screen and (max-width: 640px) and (min-width: 414px) {
html {
font-size: 22.08px;
}
}
@media only screen and (max-width: 414px) and (min-width: 375px) {
html {
font-size: 18.75px;
}
}
@media only screen and (max-width: 375px) {
html {
font-size: 17.066px;
}
}
// 這樣不是完全的運用了width/fontSize = baseWidth/baseFontSize 這個公式,只是選了幾個寬度區間
// 來設定複製程式碼
相對於根元素(即html元素)的font-size值設定好了,然後就是按照設計稿寫程式碼了,問題來了px單位轉換成rem人工計算頭有點大(在iPhone6 下面每一個都要除以20 換算出rem單位)。
比推薦的方法有兩種一種
- px2rem (npm安裝)
- Sass函式、混合巨集功能來實現
// 方法一 例子從文件上面抄下來的
.selector {
width: 150px;
border: 1px solid #ddd; /*no*/
}
//轉換過後
.selector {
width: 7.5rem;
border: 1px solid #ddd;
}
//方法二
$rem-base: 20px !default; // baseFontSize
@function rem($value, $base-value: $rem-base) {
$value: strip-unit($value) / strip-unit($base-value) * 1rem;
@if ($value == 0rem) { $value: 0; } // Turn 0rem into 0
@return $value;
}
@function strip-unit($num) {
@return $num / ($num * 0 + 1);
}
.haha {
width: rem(150); //通過 rem($value, $base-value: $rem-base) 計算出來 7.5rem。
}複製程式碼
個人更喜歡方法一,因為別人寫的UI元件通常用的是px。然後就是字型,字型大小建議不要轉rem。
用媒體查詢查詢的方法就比較要關注手機螢幕寬度了(如果做得細緻,還是要針對每個螢幕寬劃分割槽間),且對於圖片的問題還是沒有解決。
4.2viewport 縮放,rem 佈局,js計算
動態的設定根元素(即html元素)font-size的值,也有兩種方式(通常程式碼在head載入,避免頁面重繪)
// 方法一 (iPhone 6尺寸作為設計稿基準)
//document.documentElement.clientWidth /18.75
var e = (document.documentElement.clientWidth / 375) *20 ;
document.documentElement.style.fontSize = e + "px"
新增標籤到HTML
<meta name="viewport" content="width=device-width,initial-scale=1">
這樣做就使得所有螢幕都是基於iphone6的設計稿等比例顯示了
// 方法二 (iPhone 6尺寸作為設計稿基準)動態寫入 viewport 放縮
var e = (document.documentElement.clientWidth / 375) *20 ;
document.documentElement.style.fontSize = e + "px"
var initScale = 1 / window.devicePixelRatio; // initScale = 1/2;
viewPortMeta = window.document.createElement("meta");
viewPortMeta.setAttribute("name", "viewport");
viewPortMeta.setAttribute("content", "width=device-width, initial-scale=" +
initScale + ", user-scalable=no");
//iphone6 的物理畫素是 750pt*1334pt
// initScale = 1/2
// device-width = 375
// 頁面可是視口大小 = 750px; 佈局視口大小也就等於750px
// 此時一個物理畫素 對應 一個css畫素 (此時圖片模糊問題就解決了)複製程式碼
方法一:有1px物理畫素問題,和圖片問題(網上有眾多解決方法可以看看),
// JS判斷是否支援0 .5 px的邊框, 是的話, 則加上hairlines的類名。(以iphone6為例)
if (window.devicePixelRatio && devicePixelRatio >= 2) {
var testElem = document.createElement('div');
testElem.style.border = '.5px solid #000';
document.body.appendChild(testElem);
//當div存在
if (testElem.offsetHeight == 1) {
document.querySelector('html').classList.add('hairlines');
}
//新增完hairlines類名後,則刪除div
document.body.removeChild(testElem);
}
// 圖片的可以考慮實際位置,載入不同倍數的圖片(個人覺得沒有必要都用2倍圖)
// 很多網站 採用的都是這個方法複製程式碼
方法二: 安卓機的dpr神奇,且部分機型放縮情況怪異,所以通常會在iphone下考慮放縮,安卓選擇放棄,安卓機的做法就跟方法一 一樣了。
//計算 initScale 的修改
dpr = win.devicePixelRatio;
dpr = isIphone ? (dpr >= 3 ? 3 : (dpr >= 2 ? 2 : 1)) : 1;
initScale = 1 / dpr;複製程式碼
方法二:需要注意字型的問題,不建議字型跟著螢幕大小變化。通常用js的方法還會給根元素多加一個類或屬性來控制字型顯示。
//給<html>元素新增data-dpr屬性,並且動態改寫data-dpr的值,或者動態寫一個dpr的class到根元素上
//eg. <html data-dpr="1" class="dpr1">
// 動態寫dpr的class
@mixin font-dpr ($font-size) {
font-size: $font-size;
.dpr1 & {
font-size: $font-size * 1;
}
.dpr2 & {
font-size: $font-size * 2;
}
.dpr3 & {
font-size: $font-size * 3;
}
}
//動態改寫data-dpr的值
@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;
}
}複製程式碼
4.3vw適配方案(以後可能的方案)
vw unit: Equal to 1% of the width of the initial containing block.
vh unit:Equal to 1% of the height of the initial containing block.
1vw = 1%視口寬度,看到這個表達是不是心裡面一驚喜,3.2的方法就是將螢幕分成多少份,然後根元素(即html元素)的font-size值,每一份用rem來表示。現在vw的出現就更符合技術需要了因為它自動將視口寬度分成了100份。
//假設螢幕螢幕寬度 等於佈局寬度 等於可視視窗寬度。
//iPhone 6尺寸作為設計稿基準
//<meta name="viewport" content="width=device-width,initial-scale=1">
// 此時 1vw = 375/100 =3.75px;
// 此時就差將px 轉換為 vw了(此處Sass函式舉個例子)
$base_width: 375;
@function pxToVW($px) {
@return ($px / $base_width) * 100vw;
}
// iPhone 6 設計稿中 某 div 寬度為 75 px 高度 75px 表達如下
.haha {
width: pxToVW(75); // 20vw
height: pxToVW(75); // 20vw
}
//上述那段css在iPhone5下 表達的寬高是多少了
.haha {
width: pxToVW(75); // 20vw 320/100*20 = 64px
height: pxToVW(75); // 20vw 320/100*20 = 64px
}
// 看下數字題 64/75 = 320/375 設計稿在螢幕上的顯示等比放縮了複製程式碼
vw好用,但它還存在相容性問題,可以通過這個網站查閱 Can I use。不過也有大神寫了文章介紹怎麼在實際專案去使用vw。
5.小結
文中例子比較粗糙,理解不準確之處,還請教正。關於移動端適配部分方法,本文也是描述基礎思想原理,還有很多細節,相容問題沒有提及,要真的去理解它,還需多看文件,程式碼實踐。