本文原鏈接:https://www.jianshu.com/p/fa670b737a29
移動端1像素邊框問題
對於前端開發者來說,要處理這個問題,必須先補充一個知識點,就是設備的 物理像素[設備像素] & 邏輯像素[CSS像素]
- 物理像素
移動設備出廠時,不同設備自帶的不同像素,也稱硬件像素; - 邏輯像素
即css中記錄的像素。
# 為什么1px變粗了?
為什么移動端CSS里面寫了1px,實際上看起來比1px粗;了解設備物理像素和邏輯像素的同學應該很容易理解,其實這兩個px
的含義其實是不一樣的,UI設計師要求的1px是指設備的物理像素1px,而CSS里記錄的像素是邏輯像素,它們之間存在一個比例關系,可以用javascript中的window.devicePixelRatio
來獲取,也可以用媒體查詢的-webkit-min-device-pixel-ratio
來獲取。當然,比例多少與設備相關。
在手機上border無法達到我們想要的效果。這是因為devicePixelRatio特性導致,iPhone的devicePixelRatio==2,而border-width: 1px描述的是設備獨立像素,所以,border被放大到物理像素2px顯示,在iPhone上就顯得較粗。
移動端開發常需要在html的header里添加如下一句:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
這句話定義了本頁面的viewport的寬度為設備寬度,初始縮放值和最大縮放值都為1,並禁止了用戶縮放。
你的疑問可能來了,老是看到viewport,除了知道中文名叫視口,到底是啥意思?其實,它就是設備屏幕上能用來顯示我們網頁內容的那一塊區域,具體點就是瀏覽器或app中的webview用來顯示網頁的那部分區域,但viewport又不局限於瀏覽器可視區域的大小,可能大也可能小。體現在用戶是否縮放了屏幕。
心細的同學應該都有感覺,meta標簽中常設置user-scalable=no
也就是禁止用戶縮放,那用戶縮放到底會造成什么影響呢? 其實也就是顯示上的變化。縮放一倍,CSS像素(邏輯像素)所代表的物理像素也就縮放了一倍,即設備物理像素和設備獨立像素的比例增大(減小)了一倍。
或許你已經明白1px變粗的原因是啥了, viewport的設置和屏幕物理分辨率是按比例而不是相同的. 移動端window對象有個devicePixelRatio屬性, 它表示設備物理像素和css像素的比例, 在retina屏的iphone手機上, 這個值為2或3, css里寫的1px長度映射到物理像素上就有2px或3px那么長。
# 如何解決1px問題
1. 媒體查詢利用設備像素比縮放,設置小數像素
IOS8下已經支持帶小數的px值, media query
對應devicePixelRatio
有個查詢值-webkit-min-device-pixel-ratio
, css可以寫成這樣
.border { border: 1px solid #999 } @media screen and (-webkit-min-device-pixel-ratio: 2) { .border { border: 0.5px solid #999 } } @media screen and (-webkit-min-device-pixel-ratio: 3) { .border { border: 0.333333px solid #999 } }
【缺點】對設備有要求,小數像素目前兼容性較差。
2. viewport + rem 方案
該方案是對上述方案的優化,整體思路就是利用viewport + rem + js
動態的修改頁面的縮放比例,實現小於1像素的顯示。在頁面初始化時,在頭部引入原始默認狀態如下:
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
接下來的任務就是js的動態修改縮放比 以及 實現rem根元素字體大小的設置。
var viewport = document.querySelector("meta[name=viewport]") if (window.devicePixelRatio == 1) { viewport.setAttribute('content', 'width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no') } if (window.devicePixelRatio == 2) { viewport.setAttribute('content', 'width=device-width, initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no') } if (window.devicePixelRatio == 3) { viewport.setAttribute('content', 'width=device-width, initial-scale=0.333333333, maximum-scale=0.333333333, minimum-scale=0.333333333, user-scalable=no') } var docEl = document.documentElement; var fontsize = 10 * (docEl.clientWidth / 320) + 'px'; docEl.style.fontSize = fontsize;
【缺點】以為縮放涉及全局的rem單位,比較適合新項目,對於老項目可能要涉及到比較多的改動。
3. 設置 border-image 方案
.border-image-1px { border-width: 1px 0px; -webkit-border-image: url("border.png") 2 0 stretch; border-image: url("border.png") 2 0 stretch; }
【解釋】border-width
指定邊框的寬度,可以設定四個值,分別為上右下左border-width: top right bottom left
。border-image
該例意為:距離圖片上方2px(屬性值上沒有單位)裁剪邊框圖片作為上邊框,下方2px裁剪作為下邊框。距離左右0像素裁剪圖片即沒有邊框,以拉伸方式展示
結合起來就是:在邊框圖片中,裁剪圖片上下方的2個像素寬度作為上下邊框,並展示在寬度為1個像素的邊框空間里。左右沒有邊框。 注意這里的1個像素是特殊的,專指物理像素,而平時設定的長寬1px則表示邏輯像素(該觀點為個人理解)。
你可能並沒有理解,裁剪邊框是什么鬼?為啥還要裁剪?
border-image
確實是個很難理解的屬性,是去w3c看了之后更加蒙圈了。w3c內容提煉的太精致了,但解釋根本就不夠。因此奉上張鑫旭老師 border-image詳解 一文,特別要理解九宮格裁剪法。
當然,這種方式引入了圖片,我們還能將圖片裝換成base64形式表現
.border-image-1px { border-width: 1px 0px; -webkit-border-image: url("data:image/png;base64,xxx") 2 0 stretch; border-image: url('如上'); }
【缺點】需要制作圖片,圓角可能出現模糊
4. background-image 漸變實現
除了使用圖片外,當然也能使用純css來實現,百度糯米團就是采用的這種方案。
.border { background-image:linear-gradient(180deg, red, red 50%, transparent 50%), linear-gradient(270deg, red, red 50%, transparent 50%), linear-gradient(0deg, red, red 50%, transparent 50%), linear-gradient(90deg, red, red 50%, transparent 50%); background-size: 100% 1px,1px 100% ,100% 1px, 1px 100%; background-repeat: no-repeat; background-position: top, right top, bottom, left top; padding: 10px; }
【思路】將原本1個物理像素的邊框大小利用線性漸變分割成幾個部分(百分比控制),實現小於1像素效果
【解釋】linear-gradient
指定線性漸變,接受大於等於3個參數,第一個為漸變旋轉角度,第二個開始為漸變的顏色和到哪個位置(百分比)全部變為該顏色,該例子中,第一句就是,漸變方向旋轉180度,即從上往下(默認為0度從下往上),從紅色開始漸變,到50%的位置還是紅色,再漸變為繼承父元素顏色。
【缺點】因為每個邊框都是線性漸變顏色實現,因此無法實現圓角
5. box-shadow 方案
利用陰影也可以實現,優點是沒有圓角問題,缺點是顏色不好控制
div { -webkit-box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5); }
【理解】來回顧一下box-shadow
屬性的用法。
box-shadow: h-shadow v-shadow [blur] [spread] [color] [inset];
參數分別表示: 水平陰影位置,垂直陰影位置,模糊距離, 陰影尺寸,陰影顏色,將外部陰影改為內部陰影,后四個可選。該例中為何將陰影尺寸設置為負數?設置成-1px
是為了讓陰影尺寸稍小於div元素尺寸,這樣左右兩邊的陰影就不會暴露出來,實現只有底部一邊有陰影的效果。從而實現分割線效果(單邊邊框)。
6. transform: scale(0.5) 方案 - 推薦: 很靈活
在以上的用法種,無非逃不開一種思想,就是將1px縮小為0.5px來展示,然而。0.5px並不是所有的設備或瀏覽器都支持,就考慮用媒體查詢或viewport將其縮放比例。其實1像素問題的產生基本發生在設置邊框或分割線的時候,場景並不覆蓋全局樣式,因此,直接縮放需要設置的元素,才是我們真正需要的。tranform
就能實現這個需求。
- 設置
height: 1px
,根據媒體查詢結合transform
縮放為相應尺寸。
div { height:1px; background:#000; -webkit-transform: scaleY(0.5); -webkit-transform-origin:0 0; overflow: hidden; }
2.用::after
和::befor
,設置border-bottom:1px solid #000
,然后在縮放-webkit-transform: scaleY(0.5);
可以實現兩根邊線的需求
div::after{ content:'';width:100%; border-bottom:1px solid #000; transform: scaleY(0.5); }
3.用::after
設置border:1px solid #000; width:200%; height:200%,
然后再縮放scaleY(0.5);
優點可以實現圓角,京東就是這么實現的,缺點是按鈕添加active
比較麻煩。
.div::after { content: ''; width: 200%; height: 200%; position: absolute; top: 0; left: 0; border: 1px solid #bfbfbf; border-radius: 4px; -webkit-transform: scale(0.5,0.5); transform: scale(0.5,0.5); -webkit-transform-origin: top left; }
# 6.5 媒體查詢 + transfrom 對方案1的優化
/* 2倍屏 */ @media only screen and (-webkit-min-device-pixel-ratio: 2.0) { .border-bottom::after { -webkit-transform: scaleY(0.5); transform: scaleY(0.5); } } /* 3倍屏 */ @media only screen and (-webkit-min-device-pixel-ratio: 3.0) { .border-bottom::after { -webkit-transform: scaleY(0.33); transform: scaleY(0.33); } }