Css-移動端適配總結

前言

工作以後,大部分的業務工作都是基於移動端H5的,開發過程中學習了很多東西,遇到過許多問題,諸如rememcss pxdevice px等,本文純屬個人的歸納總結,如有問題,請指出親噴~

PC端

本文主要是講解移動端的響應式佈局, 但是在真正進入之前, 先了解一些概念。

device px(設備像素)和 css px(css像素)

通常在PC端上面,我們並不需要考慮設備像素和css像素之間的差別,以目前的pc來看,1個設備像素通常等於1個css像素。可以使用screen.width/height來獲取我們屏幕的寬高設備像素。

screen.width // 1920
screen.height // 1080

如果你給一個元素的寬度爲width: 192px; 那麼你的屏幕上(假設你的屏幕寬度像素爲1920)可以在一行上顯示10個該元素。原理則是因爲我們的PC中1個設備像素等於1個css像素。

當用戶放大或者縮小屏幕時(按住ctrl+滾動鼠標輪,也就是改變zoom值),則有所不同。此時,我們的設備像素仍然沒有改變,還是1920*1080,css像素的數量也沒有改變,但是css像素大小變了。 假設放大到200%, 那麼1個css像素就等於兩個設備像素, 以此類推。

以下是引用ppk大神的三張圖片, 下面深藍色爲設備像素, 上面淺藍色爲css像素

正常情況下:
image

縮小時:
image

放大時:
image

screen.width/height 和 window.innerWidth/innerHeight

screen.width/heihgt取的是屏幕的寬高,單位是是css像素。

window.innerWidth/innerHeight取的是網頁區域的寬高, 單位是css像素。

當你改變zoom值時, screen不會改變, innerWidth/height會改變

viewport的概念

viewport是一個限制html元素的功能, 可以理解爲html元素的上一層元素。聽起來有點難以理解, 下面講一個例子:

假設, 你給某個div元素設置了width:50%的樣式後, 當你縮小放大瀏覽器的時候,你會發現div元素總是佔據了50%的寬度,我們知道,寬度百分比是依賴它的包裹元素(假設是body), 那麼問題就回到了body的寬度身上。通常在沒有設置寬度的情況下,所有塊級元素都佔用其父級寬度的100%。所以body和html元素一樣寬。那麼html元素有多寬呢,默認情況下它和瀏覽器窗口一樣寬,這也就是爲什麼div總是佔據瀏覽器寬度的50%,而html元素則是受限於viewport(和viewport佔據一樣的寬度),換句話說,viewport完全等於瀏覽器窗口,而且它不是HTML語言元素,所以你無法通過使用css對其進行影響

我們可以通過document.documentElement.clientWidth/clientHeight來獲取viewport的寬高, 它的單位是css像素

clientWidth/Height 和 window.innerWidth/innerHeight

上面兩者都能夠獲取網頁區域大小, 但是他們之間還是有區別的。 前者不包含滾動條, 後者包含

html元素的大小

我們可以通過document.documentElement.offsetWidth/offsetHeight來獲取html元素的寬高, 它的單位是css像素

event事件和媒體查詢

event的三對屬性:

  • pageX/Y: 給出CSS像素中相對於html元素的座標
  • clientX/Y: 給出CSS像素中相對於viewport的座標
  • screenX/Y: 給出設備像素中相對於屏幕的座標

媒體查詢:

  • 基於viewport(documentElement .clientWidth/Height)
@media all and (max-width: 400px)
  • 基於屏幕(screen.width)
@media all and (max-device-width: 400px)

Mobile

在默認情況下,一般來講,移動設備上的viewport都是要大於瀏覽器可視區域的,這是因爲考慮到移動設備的分辨率相對於桌面電腦來說都比較小,所以爲了能在移動設備上正常顯示那些傳統的爲桌面瀏覽器設計的網站,移動設備上的瀏覽器都會把自己默認的viewport設爲980px或1024px(也可能是其它值,這個是由設備自己決定的),但帶來的後果就是瀏覽器會出現橫向滾動條,因爲瀏覽器可視區域的寬度是比這個默認的viewport的寬度要小的。下圖列出了一些設備上瀏覽器的默認viewport的寬度。

以下是關於各瀏覽器的viewport

三個viewport

前面介紹了viewport的概念, 但是在移動端的時候, viewport並不那麼容易理解, ppk在移動端提出了三個viewport的概念。

Layout viewport
也就是佈局viewport,即默認的瀏覽器viewport , 並且可以通過document.documentElement.clientWidth 來獲取。

圖片引用自深入理解viewport

visual viewport
layout viewport 的寬度是默認的瀏覽器viewport,所以我們還需要一個viewport來代表網頁區域的大小,ppk把這個viewport叫做 visual viewport。visual viewport的寬度可以通過window.innerWidth 來獲取。

圖片引用自深入理解viewport

ideal viewport
有了兩個viewport並不ok, 因爲我們既不想讓用戶滾動滾動條來瀏覽我們的網頁,也不想用戶盯着縮小了的pc網頁瀏覽, 所以有了第三個viewport。 所謂的ideal viewport則是當layout viewport等於屏幕的寬度, 如ip6,它的ideal viewport就是375px

設置viewport

開發過h5的應該都知道, 我們經常會把下面這句代碼複製到head標籤中:

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

它的作用其實就是設置了ideal viewport。以下是它的6個屬性:

key value
width 設置layout viewport 的寬度,爲一個正整數,或字符串"width-device"
initial-scale 設置頁面的初始縮放值,爲一個數字,可以帶小數
minimum-scale 允許用戶的最小縮放值,爲一個數字,可以帶小數
maximum-scale 允許用戶的最大縮放值,爲一個數字,可以帶小數
height 設置layout viewport 的高度,這個屬性對我們並不重要,很少使用
user-scalable 是否允許用戶進行縮放,值爲"no"或"yes", no 代表不允許,yes代表允許

那麼如果我們想設置ideal viewport, 只需要把width設置成width-device或者把initial-scale設置成1.0就可以了。

前者比較容易理解, 後者設置成1就可以是爲什麼? 首先要理解設置成1.0就是意味着沒有縮放,而這樣卻可以達到ideal viewport的效果, 那麼很明顯, 縮放是相對於 ideal viewport來進行縮放的,當對ideal viewport進行100%的縮放,也就是縮放值爲1的時候,不就得到了 ideal viewport。

如果兩個屬性都能設置ideal viewport, 那麼當兩個屬性衝突時怎麼解決?

遇到這種情況時,瀏覽器會取它們兩個中較大的那個值。例如,當width=400,ideal viewport的寬度爲320時,取的是400;當width=400, ideal viewport的寬度爲480時,取的是ideal viewport的寬度。

css像素和設備像素

在移動端中, 1個css像素並不等於1個設備像素, 而是取決於設備像素比(物理像素(設備像素)/獨立像素(css像素)),像Iphone的Retina屏幕, 就有2倍屏(ip6s)、3倍屏(ip6 plus), 也就是設備像素比的值分別是2和3,即1個css像素等同於4個設備像素或者9個, 如圖:

並且, 我們可以通過window.devicePixelRatio來獲取設備像素比dpr。

1px的產生和解決

問題的產生

公司的設計大佬通常給的設計稿是基於ip6s的, 也就是750px(i6s的屏幕是375px,而且是上面說的兩倍屏,所以有750個物理像素)。假設設計稿上面有個1px的border,我們通常直接這樣寫:

border {
    border: 1px solid #ccc;
}

然後設計審覈的時候就被打回來了, 因爲設計覺得變大了,也就是他覺得是2px的線了。

究其原因,是因爲設計稿是750px, 裏面的1px實際上在真機只有0.5px,所以就有了著名的1px問題。

問題的解決

1.直接使用0.5px
在iOS8下,蘋果系列都已經支持0.5px了,那麼意味着在devicePixelRatio = 2時,我們可以藉助媒體查詢來處理:著作權歸作者所有。

@media (-webkit-min-device-pixel-ratio: 2) {
    .border {
        border-width: 0.5px
    }
}

這種使用簡單,但是兼容性不太好。

2.使用border-image或者background
也就是拿一張圖片,一半透明,一半是我們想要的顏色,然後填充上去, 具體的例子就不講了, 這種基本沒啥人會用, 改個顏色還要修改圖片,太麻煩了。

3.box-shadow

.box-shadow-1px {
    box-shadow: inset 0px -1px 1px -1px #c8c7cc;
}

這種顏色有陰影, 估計過不了設計大佬的那關。

4.PostCSS的插件postcss-write-svg
直接藉助插件幫助我們實現, 其實也就是postcss幫我們生成圖片而已。

@svg 1px-border { 
    height: 2px; 
    @rect { 
        fill: var(--color, black); width: 100%; height: 50%; 
    } 
} 
.example { border: 1px solid transparent; 
           border-image: svg(1px-border param(--color #00b1ff)) 2 2 stretch;
}

最後Postcss會把對應的css編譯出來, 這種兼容性好, 就是依賴於插件。

.example { border: 1px solid transparent; 
           border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch
}

5.僞類 + transform 實現
原理是把原先元素的 border 去掉,然後利用 :before 或者 :after 重做 border ,並 transform 的 scale 縮小一半,原先的元素相對定位,新做的 border 絕對定位。

.scale-1px{
position: relative;
border:none;
}
.scale-1px:after{
content: '';
position: absolute;
bottom: 0;
background: #000;
width: 100%;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}

這種兼容好, 但是會和僞類衝突, 也是我司採用的方式。

6.縮小viewport
原理是使用meta標籤中的viewport, 也就是上面所說的設置viewport, 將整個頁面縮小0.5倍, 這個主要是麻煩在其他的元素也要跟着放大一倍再縮小, 爲了這個小問題而這樣, 似乎有點得不償失。

然而, 淘寶的flexible方案(rem佈局,見下文)幫我們搞定了這個問題。

flexible和rem

上面提到了flexible和rem的佈局方案, 在剛推出的時候, 確實很火, 公司的一些項目目前仍然是採用該方案, 這裏簡單的說下其原理。

px、em、rem

  • px: px在前面已經講了, 就是一個固定單位。
  • em: em作爲font-size的單位時,其代表父元素的字體大小,em作爲其他屬性單位時,代表自身字體大小。
  • rem: rem作用於非根元素時,相對於根元素字體大小;rem作用於根元素字體大小時,相對於其出初始字體大小。
/* 作用於根元素,相對於原始大小(16px),所以html的font-size爲32px*/
html {font-size: 2rem}

/* 作用於非根元素,相對於根元素字體大小,所以爲64px */
p {font-size: 2rem}

flexible rem佈局原理
flexible rem佈局原理即是把設計稿等比寬的切成100份, 假設每份的單位是x, 那麼我們在佈局的時候就可以以x爲單位, 將設計稿等比例的放大縮小到對應的屏幕了,這樣就不用爲各個屏幕做適配。

然而像上面所說的x是不存在的, 不過好在我們有rem, 只要我們將rem設置成1x,那麼開發過程中,不就達到我們的目的了嗎?

如何將rem設成1x呢? 回想一下, 我們是不是能取得viewport的寬度(document.documentElement.clientWidth),我們能取得設備像素比window.devicePixelRatio), 我們能夠設置html的樣式(html.style.fontSize = '...'), 所以簡單的實現方案就有了

document.documentElement.style.fontSize = document.documentElement.clientWidth / 100 + 'px';

當然,flexible並不這麼簡單,它還對於不同dpr做了處理, 它幫你處理了2倍屏、3倍屏等情況,通過設置viewport的縮放比,這也就是上面所說的0.5px處理方案之一, 具體的這裏不再贅述, 有興趣的同學可以看看原文。

最後,移動端 iOS 8 以上以及 Android 4.4 以上已經有了vw\vh單位, 1vw1vh相當於viewport的百分之一寬/高,也就是我們上面所說的x單位, 如果你的手機支持該api, 也可以使用該單位方案。

爲什麼不用rem方案

依稀記得, 某次使用了rem處理活動頁的時候, 被設計大佬駁回了。
大佬認爲, 當用戶使用更大的屏幕的時候, 他應該能看到更多的內容, 而且設計稿被放大或者縮小的話, 會失去他原來的感覺
所以, 對於rem方案其實可能已經不太適合當前的情況了, 畢竟使用媒體查詢和px以及em就能解決各種響應式問題, 雖然效率會比較低下, 而關於這個, 也恰好看到了有人在知乎上提了這麼個問題, 有興趣的可以前去圍觀。

爲什麼很多web項目還是使用 px,而不是 rem?

總結

本文多是概念上的,也參考了許多文章,要真正理解還需要多參考實際項目。

參考&引用

移動前端開發之viewport的深入理解
A tale of two viewports — part one
A tale of two viewports — part two
Meta viewport
7 種方法解決移動端 Retina 屏幕 1px 邊框問題
再談Retina下1px的解決方案
vh,vw單位你知道多少?
Rem佈局的原理解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章