現在的PM和UI總以看app的眼光看html5, html頁面要做的專業美觀,而且必須很精細.
去年的時候UI就告訴我h5上的邊框線太粗,把整站都給拉low了. 當時工期緊就沒太在意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: 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 } }
如果使用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; } }
需要注意<input type="button">是沒有:before, :after偽元素的
優點: 其實不止是圓角, 其他的邊框也可以這樣做出來
缺點: 代碼量也很大, 占據了偽元素, 容易引起沖突
5. flexible.js
這是淘寶移動端采取的方案, github的地址:https://github.com/amfe/lib-flexible. 前面已經說過1px變粗的原因就在於一刀切的設置viewport寬度, 如果能把viewport寬度設置為實際的設備物理寬度, css里的1px不就等於實際1px長了么. flexible.js就是這樣干的.
<meta name=”viewport”>里面的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和rem相互轉換的計算方法會暴露在window.lib.flexible中. 這樣可以為less/sass編寫宏方法. 具體的css改寫方法參照大漠的文章http://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html
項目中特別指出了為了防止字體模糊, 出現奇數字號的字體, 字體的實際單位還是要以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);