前言
工作以后,大部分的業務工作都是基於移動端H5的,開發過程中學習了很多東西,遇到過許多問題,諸如rem\em\css px\device 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像素
正常情況下:
縮小時:
放大時:
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呢? 回想一下, 我們是不是能取得viewpor
t的寬度(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
單位, 1vw\1vh相當於viewport的百分之一寬/高
,也就是我們上面所說的x單位, 如果你的手機支持該api, 也可以使用該單位方案。
為什么不用rem方案
依稀記得, 某次使用了rem處理活動頁的時候, 被設計大佬駁回了。
大佬認為, 當用戶使用更大的屏幕的時候, 他應該能看到更多的內容
, 而且設計稿被放大或者縮小的話, 會失去他原來的感覺
。
所以, 對於rem方案其實可能已經不太適合當前的情況了, 畢竟使用媒體查詢和px以及em就能解決各種響應式問題, 雖然效率會比較低下, 而關於這個, 也恰好看到了有人在知乎上提了這么個問題, 有興趣的可以前去圍觀。
總結
本文多是概念上的,也參考了許多文章,要真正理解還需要多參考實際項目。
參考&引用
移動前端開發之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布局的原理解析