本文介紹了移動端適配的3種方法,以及移動端圖片模糊問題和1px細線問題的解決方法。當然了,在這之前先整理了與這些方法相關的知識:物理像素、設備獨立像素、設備像素比和viewport。
>>>>物理像素、設備獨立像素和設備像素比
在CSS中我們一般使用px作為單位,需要注意的是,CSS樣式里面的px和物理像素並不是相等的。CSS中的像素只是一個抽象的單位,在不同的設備或不同的環境中,CSS中的1px所代表的物理像素是不同的。在PC端,CSS的1px一般對應着電腦屏幕的1個物理像素,但在移動端,CSS的1px等於幾個物理像素是和屏幕像素密度有關的。
物理像素(physical pixel)
物理像素又被稱為設備像素、設備物理像素,它是顯示器(電腦、手機屏幕)最小的物理顯示單位,每個物理像素由顏色值和亮度值組成。所謂的一倍屏、二倍屏(Retina)、三倍屏,指的是設備以多少物理像素來顯示一個CSS像素,也就是說,多倍屏以更多更精細的物理像素點來顯示一個CSS像素點,在普通屏幕下1個CSS像素對應1個物理像素,而在Retina屏幕下,1個CSS像素對應的卻是4個物理像素(參照下文田字示意圖理解)。
設備獨立像素(device-independent pixel)
設備獨立像素又被稱為CSS像素,是我們寫CSS時所用的像素,它是一個抽像的單位,主要使用在瀏覽器上,用來精確度量Web頁面上的內容。
設備像素比(device pixel ratio)
設備像素比簡稱為dpr,定義了物理像素和設備獨立像素的對應關系:設備像素比 = 物理像素 / 設備獨立像素。
CSS的1px等於幾個物理像素,除了和屏幕像素密度dpr有關,還和用戶縮放有關系。例如,當用戶把頁面放大一倍,那么CSS中1px所代表的物理像素也會增加一倍;反之把頁面縮小一倍,CSS中1px所代表的物理像素也會減少一倍。關於這點,在文章后面的1px細線問題部分還會講到。
>>>>viewport
viewport就是設備上用來顯示網頁的那一塊區域,但viewport又不局限於瀏覽器可視區域的大小,它可能比瀏覽器的可視區域要大,也可能比瀏覽器的可視區域要小。在默認情況下,一般來講,移動設備上的viewport都是要大於瀏覽器可視區域的,這是因為考慮到移動設備的分辨率相對於桌面電腦來說都比較小,所以為了能在移動設備上正常顯示那些傳統的為桌面瀏覽器設計的網站,移動設備上的瀏覽器都會把自己默認的viewport設為980px或1024px(也可能是其它值,這個是由設備自己決定的),但帶來的后果就是瀏覽器會出現橫向滾動條,因為瀏覽器可視區域的寬度是比這個默認的viewport的寬度要小的。
明確三種不同的viewport視口:
visual viewport 可見視口,指屏幕寬度
layout viewport 布局視口,指DOM寬度
ideal viewport 理想適口,使布局視口就是可見視口即為理想適口
獲取屏幕寬度(visual viewport)的尺寸:
window. innerWidth/Height
獲取DOM寬度(layout viewport)的尺寸:
document. documentElement. clientWidth/Height
設置理想視口ideal viewport:
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
該meta標簽的作用是讓layout viewport的寬度等於visual viewport的寬度,同時不允許用戶手動縮放,從而達到理想視口。
meta[name="viewport"]里各參數的含義為:
width: 設置layout viewport 的寬度,為一個正整數,或字符串”width-device”。
initial-scale: 設置頁面的初始縮放值,為一個數字,可以帶小數。
minimum-scale: 允許用戶的最小縮放值,為一個數字,可以帶小數。
maximum-scale: 允許用戶的最大縮放值,為一個數字,可以帶小數。
height: 設置layout viewport 的高度,這個屬性對我們並不重要,很少使用。
user-scalable: 是否允許用戶進行縮放,值為“no”或“yes”。
>>>>rem適配方案
適配是為了使頁面在不同手機設備上,相對保持統一的效果。移動端自適應方案很多,有百分比布局,彈性盒模型布局等,但是最好用的要數rem布局了。
rem是相對於根元素的字體大小的單位,我們可以根據設備寬度動態設置根元素的font-size,使得以rem為單位的元素在不同終端上以相對一致的視覺效果呈現。下面介紹3種根據屏幕寬度設置rem基准值的方法。(注:為了換算方便,以下三種方法都用1:100的比例,即1rem=100px。)
用JS設置rem基准值
/* 設計稿是750,采用1:100的比例,用1rem表示100px,font-size為100 * (clientWidth / 750) */ (function(doc, win) { var docEl = doc.documentElement, resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize', recalc = function() { var clientWidth = docEl.clientWidth; if (!clientWidth) return; docEl.style.fontSize = 100 * (clientWidth / 750) + 'px'; }; if (!doc.addEventListener) return; win.addEventListener(resizeEvt, recalc, false); doc.addEventListener('DOMContentLoaded', recalc, false); })(document, window);
用密集的媒體查詢設置font-size
/* 設計稿是750,采用1:100的比例,用1rem表示100px,100*(100/750)=13.333 以min-width: 750px時font-size: 100px為基准,
min-width每縮小100px,font-size就縮小13.3333px,如需更密集的媒體查詢可以按照這個對照關系設置。*/
@media screen and (min-width: 320px) {
html {
font-size: 42.6667px;
}
}
@media screen and (min-width: 375px) {
html {
font-size: 50px;
}
}
@media screen and (min-width: 425px) {
html {
font-size: 56.6667px;
}
}
@media screen and (min-width: 768px) {
html {
font-size: 102.4px;
}
}
用單位vw設置font-size
1vw等於屏幕可視區寬度(的可視區域的百分之一。
/* 設計稿是750,采用1:100的比例,用1rem表示100px,font-size為100*(100vw/750) */
html {
font-size: 13.3334vw;
}
注:兼容性不是很好。
了解了物理像素、設備獨立像素、設備像素比和viewport這幾個重要概念后,來看一下移動端開發中,由於屏幕分辨率導致的兩個經典問題:圖片模糊問題和1px細線問題。(注:為了敘述簡潔,以下多倍屏均只敘述2倍Retina屏,其它屏幕同理。)
>>>>圖片模糊問題
一個位圖像素是柵格圖像(如:png, jpg, gif等)最小的數據單元。每一個位圖像素都包含着一些自身的顯示信息(如:顯示位置,顏色值,透明度等)。理論上,1個位圖像素對應於1個物理像素,圖片才能得到完美清晰的展示。對於dpr=2的Retina屏幕而言,1個位圖像素對應於4個物理像素,由於單個位圖像素不可以再進一步分割,所以只能就近取色,導致圖片看起來比較模糊,如下圖。
對於圖片模糊問題,比較好的方案就是用多倍圖片(@2x)。如:一個200×300(CSS pixel)的img標簽,對於dpr=2的屏幕,用400×600的圖片,如此一來,位圖像素點個數就是原來的4倍,在Retina屏幕下,位圖像素點個數就可以跟物理像素點個數形成 1 : 1的比例,圖片自然就清晰了。
如果普通屏幕下,也用了兩倍圖片,會怎樣呢?
在普通屏幕下,200×300(CSS pixel)img標簽,所對應的物理像素個數就是200×300個,而兩倍圖片的位圖像素個數是200×300×4個,所以就出現一個物理像素點對應4個位圖像素點,但它的取色也只能通過一定的算法取某一個位圖像素點上的色值,這個過程叫做(downsampling),肉眼看上去雖然圖片不會模糊,但是會覺得圖片缺少一些銳利度,或者是有點色差,如下圖。
所以最好的解決辦法是:不同的dpr下,加載不同的尺寸的圖片。不管是通過CSS媒體查詢,還是通過JS條件判斷都是可以的。
>>>>1px細線問題
在上文我們已經知道,CSS像素為1px寬的直線,對應的物理像素是不同的,可能是2px或者3px,而設計師想要的1px寬的直線,其實就是1物理像素寬,如下圖。
對於CSS而言,可以認為是border: 0.5px;,這是多倍屏下能顯示的最小單位。然而,並不是所有手機瀏覽器都能識別border: 0.5px,有的系統里,0.5px會被當成為0px處理,那么如何實現這0.5px呢?網上有很多解決方法,比如border-image 圖片、background-image 漸變、box-shadow 等,因為這些方案不太好,所以不做贅述了,我推薦兩種方法:用媒體查詢根據dpr用“偽元素+transform”對邊框進行縮放;用JS根據屏幕尺寸和dpr精確地設置不同屏幕所應有的rem基准值和initial-scale縮放值。
偽元素+transform
構建1個偽元素, border為1px, 再以transform縮放到50%。
/* 設計稿是750,采用1:100的比例,font-size為100*(100vw/750) */
.border-1px {
position: relative;
}
@media screen and (-webkit-min-device-pixel-ratio: 2) {
.border-1px:before {
content: " ";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
border-top: 1px solid #D9D9D9;
color: #D9D9D9;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
用JS計算rem基准值和viewport縮放值
/* 設計稿是750,采用1:100的比例,font-size為100 * (docEl.clientWidth * dpr / 750) */ var dpr, rem, scale; var docEl = document.documentElement; var fontEl = document.createElement('style'); var metaEl = document.querySelector('meta[name="viewport"]'); dpr = window.devicePixelRatio || 1; rem = 100 * (docEl.clientWidth * dpr / 750); scale = 1 / dpr; // 設置viewport,進行縮放,達到高清效果 metaEl.setAttribute('content', 'width=' + dpr * docEl.clientWidth + ',initial-scale=' + scale + ',maximum-scale=' + scale + ', minimum-scale=' + scale + ',user-scalable=no'); // 設置data-dpr屬性,留作的css hack之用,解決圖片模糊問題和1px細線問題 docEl.setAttribute('data-dpr', dpr); // 動態寫入樣式 docEl.firstElementChild.appendChild(fontEl); fontEl.innerHTML = 'html{font-size:' + rem + 'px!important;}';
相較與於上文rem適配方案里“用JS計算rem基准值”的方案,這個“用JS計算rem基准值和viewport縮放值”的方案可以解決1px細線問題。表格以2倍Retina屏做比較,其他多倍屏同理。
用JS根據屏幕尺寸和dpr精確地設置不同屏幕所應有的rem基准值和initial-scale縮放值,這個JS方案已經在完美解決了1px細線問題,我們不需要再做任何事情,至於圖片模糊問題,只需要根據data-dpr的值動態加載不同尺寸的圖就可以了。
分享一個公眾號-----前端麻辣燙 ,一個專注於前端技術學習與交流的公眾號~
微信搜索“WebSnacks”,或者掃描下方二維碼。