移動端1px細線解決方案總結
移動端1px變粗的原因
為什麼移動端css裡面寫了1px, 實際看起來比1px粗. 其實原因很好理解:這2個’px’的含義是不一樣的. 移動端html的header總會有一句
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
這句話定義了本頁面的viewport的寬度為裝置寬度,初始縮放值和最大縮放值都為1,並禁止了使用者縮放. viewport通俗的講是瀏覽器上可用來顯示頁面的區域, 這個區域是可能比螢幕大的.
根據這篇文章http://www.cnblogs.com/2050/p/3877280.html的分析, 手機存在一個能完美適配的理想viewport, 解析度相差很大的手機的理想viewport的寬度可能是一樣的, 這樣做的目的是為了保證同樣的css在不同螢幕下的顯示效果是一致的, 上面的meta實際上是設定了ideal viewport的寬度.
以實際舉例: iphone3和iphone4的螢幕寬度分別是320px,640px, 但是它們的ideal viewport的寬度都是320px, 設定了裝置寬度後, 320px寬的元素都能100%的填充滿螢幕寬. 不同手機的ideal viewport寬度是不一樣的, 常見的有320px, 360px, 384px. iphone系列的這個值在6之前都是320px, 控制viewport的好處就在於一套css可以適配多個機型.
看懂的人應該已經明白 1px變粗的原因了, viewport的設定和螢幕物理解析度是按比例而不是相同的. 移動端window物件有個devicePixelRatio屬性, 它表示裝置物理畫素和css畫素的比例, 在retina屏的iphone手機上, 這個值為2或3, css裡寫的1px長度對映到物理畫素上就有2px或3px那麼長.
1px解決方案
1.用小數來寫px值
IOS8下已經支援帶小數的px值, media query對應devicePixelRatio有個查詢值-webkit-min-device-pixel-ratio, css可以寫成這樣
.border { border: 1 px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2),
@media screen and (min-device-pixel-ratio: 2){
.border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3),
@media screen and (min-device-pixel-ratio: 3) {
.border { border: 0.333333px solid #999 }
}
如果使用less/sass的話只是加了1句mixin
缺點: 安卓與低版本IOS不適用, 這個或許是未來的標準寫法, 現在不做指望
2.border-image
這樣的1張6X6的圖片, 9宮格等分填充border-image, 這樣元素的4個邊框寬度都只有1px
@media screen and (-webkit-min-device-pixel-ratio: 2){
.border{
border: 1px solid transparent;
border-image: url(border.gif) 2 repeat;
}
}
圖片可以用gif, png, base64多種格式, 以上是上下左右四條邊框的寫法, 需要單一邊框只要定義單一邊框的border, 程式碼比較直觀.
border-image相容性:
缺點: 對於圓角樣式, 將圖片放大修改成圓角也能滿足需求, 但這樣無形中增加了border的寬度,存在多種邊框顏色或者更改的時候麻煩
3 background漸變
背景漸變, 漸變在透明色和邊框色中間分割, frozenUI用的就是這種方法, 借用它的上邊框寫法:
@media screen and (-webkit-min-device-pixel-ratio: 2){
.ui-border-t {
background-position: left top;
background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0.5,transparent),color-stop(0.5,#e0e0e0),to(#e0e0e0));
}
}
這樣更改顏色比border-image方便, 相容性
缺點: 程式碼量大, 而且需要針對不同邊框結構, frozenUI就定義9種基本樣式,而且這只是背景, 這樣做出來的邊框實際是在原本的border空間內部的, 如果元素背景色有變化的樣式, 邊框線也會消失.最後不能適應圓角樣式
4 :before, :after與transform
之前說的frozenUI的圓角邊框就是採用這種方式, 構建1個偽元素, 將它的長寬放大到2倍, 邊框寬度設定為1px, 再以transform縮放到50%.
.radius-border{
position: relative;
}
@media screen and (-webkit-min-device-pixel-ratio: 2){
.radius-border:before{
content: "";
pointer-events: none; /* 防止點選觸發 */
box-sizing: border-box;
position: absolute;
width: 200%;
height: 200%;
left: 0;
top: 0;
border-radius: 8px;
border:1px solid #999;
-webkit-transform(scale(0.5));
-webkit-transform-origin: 0 0;
transform(scale(0.5));
transform-origin: 0 0;
}
}
需要注意是沒有:before, :after偽元素的
優點: 其實不止是圓角, 其他的邊框也可以這樣做出來
缺點: 程式碼量也很大, 佔據了偽元素, 容易引起衝突
5 flexible.js
這是淘寶移動端採取的方案, github的地址:https://github.com/amfe/lib-flexible. 前面已經說過1px變粗的原因就在於一刀切的設定viewport寬度, 如果能把viewport寬度設定為實際的裝置物理寬度, css裡的1px不就等於實際1px長了麼. flexible.js就是這樣乾的.
裡面的scale值指的是對ideal viewport的縮放, flexible.js檢測到IOS機型, 會算出scale = 1/devicePixelRatio, 然後設定viewport
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
devicePixelRatio=2時輸出meta如下, 這樣viewport與ideal viewport的比是0.5, 也就與裝置物理畫素一致
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
另外html元素上的font-size會被設定為螢幕寬的1/10, 這樣css可以以rem為基礎長度單位進行改寫, 比如rem是28px, 原先的7px就是0.25rem. border的寬度能直接寫1px.
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) { //大於540px可以不認為是手機屏
width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
專案中特別指出了為了防止字型模糊, 出現奇數字號的字型, 字型的實際單位還是要以px為單位.
缺點: 不適用安卓, flexible內部做了檢測 非iOS機型還是採用傳統的scale=1.0, 原因在於安卓手機不一定有devicePixelRatio屬性, 就算有也不一定能響應scale小於1的viewport縮放設定, 例如我的手機設定了scale=0.33333333, 顯示的結果也與scale=1無異.
綜合使用
對於IOS, flexible.js處理的已經很好了, 對於Android,方法2,3,4結合起來大體可以滿足要求. flexible.js雖然不適用於安卓, 但它裡面的這一段程式碼可以用來做對安卓機的部署.
if (!dpr && !scale) {
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下,對於2和3的屏,用2倍的方案,其餘的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他裝置下,仍舊使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}
這裡對安卓做檢測, 如果是安卓, js動態載入css.
var link = document.createElement('link');
link.setAttribute("rel","stylesheet");
link.setAttribute("type","text/css");
link.setAttribute("href",".......Android.css");
document.querySelector('head').appendChild(link);