1px 問題指的是:在一些?Retina屏幕
?的機型上,移動端頁面的 1px 會變得很粗,呈現出不止 1px 的效果。原因很簡單——CSS 中的 1px 并不能和移動設備上的 1px 劃等號。它們之間的比例關系有一個專門的屬性來描述:
window.devicePixelRatio = 設備的物理像素 / CSS像素。
打開 Chrome 瀏覽器,啟動移動端調試模式,在控制臺去輸出這個?devicePixelRatio
?的值。這里選中 iPhone6/7/8 這系列的機型,輸出的結果就是2:?
?這就意味著設置的 1px CSS 像素,在這個設備上實際會用 2 個物理像素單元來進行渲染,所以實際看到的一定會比 1px 粗一些。?
解決1px 問題的三種思路:
思路一:直接寫 0.5px
如果之前 1px 的樣式這樣寫:
border:1px solid #333
可以先在 JS 中拿到 window.devicePixelRatio 的值,然后把這個值通過 JSX 或者模板語法給到 CSS 的 data 里,達到這樣的效果(這里用 JSX 語法做示范):
<div id="container" data-device={{window.devicePixelRatio}}></div>
然后就可以在 CSS 中用屬性選擇器來命中 devicePixelRatio 為某一值的情況,比如說這里嘗試命中 devicePixelRatio 為2的情況:
#container[data-device="2"] {border:0.5px solid #333
}
直接把 1px 改成 1/devicePixelRatio 后的值,這是目前為止最簡單的一種方法。這種方法的缺陷在于兼容性不行,IOS 系統需要8及以上的版本,安卓系統則直接不兼容。
思路二:偽元素先放大后縮小
這個方法的可行性會更高,兼容性也更好。唯一的缺點是代碼會變多。
思路是先放大、后縮小:在目標元素的后面追加一個 ::after 偽元素,讓這個元素布局為 absolute 之后、整個伸展開鋪在目標元素上,然后把它的寬和高都設置為目標元素的兩倍,border值設為 1px。接著借助 CSS 動畫特效中的放縮能力,把整個偽元素縮小為原來的 50%。此時,偽元素的寬高剛好可以和原有的目標元素對齊,而 border 也縮小為了 1px 的二分之一,間接地實現了 0.5px 的效果。
代碼如下:
#container[data-device="2"] {position: relative;
}
#container[data-device="2"]::after{position:absolute;top: 0;left: 0;width: 200%;height: 200%;content:"";transform: scale(0.5);transform-origin: left top;box-sizing: border-box;border: 1px solid #333;}
}
思路三:viewport 縮放來解決
這個思路就是對 meta 標簽里幾個關鍵屬性下手:
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
這里針對像素比為2的頁面,把整個頁面縮放為了原來的1/2大小。這樣,本來占用2個物理像素的 1px 樣式,現在占用的就是標準的一個物理像素。根據像素比的不同,這個縮放比例可以被計算為不同的值,用 js 代碼實現如下:
const scale = 1 / window.devicePixelRatio;
// 這里 metaEl 指的是 meta 標簽對應的 Dom
metaEl.setAttribute('content', `width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale}`);
這樣解決了,但這樣做的副作用也很大,整個頁面被縮放了。這時 1px 已經被處理成物理像素大小,這樣的大小在手機上顯示邊框很合適。但是,一些原本不需要被縮小的內容,比如文字、圖片等,也被無差別縮小掉了。