1. 程式人生 > >移動端1px細線解決方案總結

移動端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

<code>這裡寫圖片描述</code>

這樣的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);