新聞中心
為了便于更好的理解本文,下面對像素相關概念進行梳理。

|
像素 |
px |
是圖像顯示的基本單元,相對單位。 | ||||
|
設備像素(物理像素) |
dp |
device pixels,顯示屏就是由一個個物理像素點組成,屏幕從工廠出來那天起物理像素點就固定不變了。也就是我們經(jīng)??吹降氖謾C分辨率所描述的數(shù)字。 | ||||
|
設備獨立像素(邏輯像素) |
dip |
device-independent pixels,就是我們手機的實際視口大小。是操作系統(tǒng)為了方便開發(fā)者而提供的一種抽象。程序與操作系統(tǒng)之間描述長度是以設備獨立像素為單位。不隨頁面縮放、瀏覽器窗口大小而改變。 | ||||
|
CSS像素 |
| |||||
|
設備像素比 |
dpr |
devicePixelRatio,是物理像素和設備獨立像素的比值。 | ||||
|
屏幕尺寸 |
inch |
屏幕對角線長度 | ||||
|
屏幕分辨率 |
Resoution |
750*1334,手機屏幕縱、橫方向像素點數(shù),單位是px。常說的分辨率指的就是物理像素。相同大小的屏幕而言,屏幕分辨率越高顯示的像素越多,單個像素尺寸較小,顯示效果就越精細。 | ||||
|
像素密度 |
dpi/ppi |
|
簡單來說就是像素單位基本分為三種:設備像素(物理像素)、設備獨立像素(邏輯像素)、CSS 像素。下文將會圍繞相關概念展開討論。
話不多說,正文開始~~
為什么使用 1px 會出現(xiàn)問題
自從 2010 年 iPhone4 推出了 Retina 屏開始,移動設備屏幕的像素密度越來越高,于是便有了 2 倍屏、3 倍屏的概念。簡單來說,就是手機屏幕尺寸沒有發(fā)生變化,但屏幕的分辨率卻提高了一倍,即同樣大小的屏幕上,像素多了一倍。
那么我們獲取到的 CSS 像素就不是真實的物理像素點了,于是便有了設備像素比的概念( ??devicePixelRatio?? 簡稱 dpr)。它用來描述屏幕物理像素與邏輯像素的比值。不同手機有不同的設備像素比,可參考 ?wiki 百科中對視網(wǎng)膜屏的描述 。
CSS 中的 1px 并不等于設備的 1px
對于前端來說,在高清屏出現(xiàn)之前,前端代碼的 ??1px??? 即等于手機物理像素點的 ??1px???。但有了 dpr 的概念之后,由于前端代碼中的使用的是 CSS 像素,手機會根據(jù) dpr 換算成實際的物理像素大小來渲染頁面。比如 iPhone6 的設備像素比 ??dpr = 2??? ,相當于一個 CSS 像素等于兩個物理像素,即 ??1px?? 由 2個物理像素點組成。
那么問題來了,以 iPhone6 為例,其 ??dpr = 2???、屏幕尺寸(CSS 像素) 為 ??375x667???,一般設計稿提供 2 倍圖尺寸為 ??750x1334??? 。那么設計稿中的 ??1px???,對應屏幕尺寸其實應該寫成 ??0.5px???。再由 dpr 計算公式可知,??0.5 * 2 = 1px?? 物理像素。
此時你應該已經(jīng)發(fā)現(xiàn)了,設計稿要實現(xiàn) ??1px??? 細線、??1px??? 邊框,為什么前端實現(xiàn)總是偏粗的?那是因為如果你在代碼中直接寫成 ??1px???,再通過 dpr 計算之后其實是 ??2px?? 物理像素,并不符合設計稿的要求。
其實設計稿本質(zhì)上要實現(xiàn)的是 CSS 像素的 !
那么當 ??dpr=2??? 時,代碼中直接寫成 ??0.5px?? 就解決問題了嗎?
小數(shù)點像素 0.5px 的兼容性問題
其實在項目中,我們已經(jīng)采用 rem 單位進行了設計稿與屏幕尺寸的換算,即把 ??1px??? 換算成了 ??0.5px?? 。但這種方案其實有各種各樣的兼容性問題。
PC端
先上結論,在 PC 端瀏覽器的最小識別像素為 ??1px??。
所以在開發(fā)階段,當在開發(fā)者工具上進行頁面調(diào)試時,可以看到即便代碼中是 ??0.5px??? ,但默認會被瀏覽器識別并渲染為 ??1px??。所以在瀏覽器看來總是偏粗了。如果你習慣用 PC 端的頁面來進行視覺走查,那么結果可想而知...
上圖中兩個元素 ??width:200px;height:100px???,分別為 ??border:0.5px???、??border:1px??? ,檢查元素時可以看到頁面上 ??border=0.5px??? 的元素計算大小后和 ??1px??? 效果是一樣的,均是 ??width:202px;height:102px???。說明瀏覽器都識別成 ??1px?? 了。
由上面的 gif 圖也可以看到,因為設備 ??dpr=2???,所以放大后 ??1px??? 的確是使用了2個物理像素點來渲染。并不是我們想實現(xiàn)的 ??0.5px??。
移動端
在手機端,不同手機瀏覽器對小數(shù)點像素的處理效果就更千奇百怪了。
首先我們先來看一下采用 REM 布局方式下,代碼中的 0.01rem 到底被換算成了多少?
這里簡單說下 REM 實現(xiàn)原理
??rem(font size of the root element)???,即根據(jù)網(wǎng)頁的根元素(??html???)來設置字體大小。和 ??em(font size of the element)??? 的區(qū)別是,??em?? 是根據(jù)其父元素的字體大小來進行設置。
簡單來說,rem 布局實現(xiàn)移動端適配的思想是,由于 rem 單位是根據(jù)頁面根元素的 ??fontSize??? 來計算的,那么將 ??fontSize??? 設置成屏幕寬度 ??clientWidth??? 與設計稿寬度 ??750??? 的比值,那么我們按照設計稿的尺寸來重構頁面的時候,使用 rem 單位即自動乘以 ??fontSize?? 計算出了適配不同屏幕的尺寸。
// 以750設計稿為例,計算rem font-size
let clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
let ft = (clientWidth / 7.5).toFixed(2);
// 設置頁面根字號大小
document.documentElement.style.fontSize = ft + "px";
由上面的計算方式可知,不同屏幕寬度會計算出不同 ??fontSize??? ,那么 ??0.01rem?? 到底被換算成了多少呢?下面舉例計算了幾個機型的“1像素”大小
由表格可看出,不同手機計算的“1px”大小差別很大,而且手機本身對小數(shù)點的處理情況就存在較大的兼容性問題。
比如 IOS8+ 系列都已經(jīng)支持 ??0.5px??? 了,可以借助媒體查詢來處理,但是安卓手機對小數(shù)像素的表現(xiàn)形式卻各不相同。網(wǎng)上關于不同型號手機瀏覽器對小數(shù)點的處理情況的資料較少,只知道在一些低版本的系統(tǒng)里,??0.5px??? 將會被顯示為 ??0px???;有的能夠畫出半個像素的邊,有的大于 ??0.55px??? 當成 ??1px???,有的大于 ??0.75px??? 當成 ??1px??,從表格計算結果來看是很難直接實現(xiàn)適配的。
比如 HUAWAI P30 的 ??0.01rem??? 計算后為 ??0.48px??? ,這種較小的小數(shù)像素其 ??border?? 已經(jīng)無法正常展示了。
那么如何實現(xiàn) 1px 的效果?
在進行一番調(diào)研之后,發(fā)現(xiàn)目前的實現(xiàn)方案都離不開以下三種。
- 使用?
?偽元素 + CSS3``縮放??的方式- 使用?
?動態(tài) viewport + rem 布局??? 的方式(即??Flexible?? 實現(xiàn)方案)- 新方案:使用?
?vw 單位??適配方案(將來推薦的一種方案,但目前項目中沒有實際應用,故本文不做討論)
1. 偽元素 + CSS3縮放
其實這種方案也是大家在項目中經(jīng)常使用的方式。文本主要對其實現(xiàn)原理進行分析。
前面已經(jīng)討論過要實現(xiàn)設計稿中的 ??1px???,其實代碼中要實現(xiàn) ??0.5px?? ??s放的方式就是避免了直接寫小數(shù)像素帶來的不同手機的兼容性處理不同。先上代碼:
// 通過偽元素實現(xiàn) 0.5px border
.border::after {
content: "";
box-sizing: border-box; // 為了與原元素等大
position: absolute;
left: 0;
top: 0;
width: 200%;
height: 200%;
border: 1px solid gray;
transform: scale(0.5);
transform-origin: 0 0;
}
// 通過偽元素實現(xiàn) 0.5px 細線
.line::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 1px;
background: #b3b4b8;
transform: scale(0.5);
transform-origin: 0 0;
}
// dpr適配可以這樣寫
@media (-webkit-min-device-pixel-ratio: 2) {
.line::after {
...
height: 1px;
transform: scale(0.5);
transform-origin: 0 0;
}
}
@media (-webkit-min-device-pixel-ratio: 3) {
.line::after {
...
height: 1px;
transform: scale(0.333);
transform-origin: 0 0;
}
}
為什么要先放大 200% 再縮小 0.5?
為了只縮放 ??border??? 1px 的粗細,而保證 ??border??? 的大小不變。如果直接 ??scale(0.5)??? 的話 ??border??? 整體大小也會變成二分之一,所以先放大 200%(放大的時候 ??border?? 的粗細是不會被放大的)再縮放,就能保持原大小不變了。
為什么采用縮放的方式,就可以解決手機對小數(shù)點處理的兼容性問題?
此處我是這樣理解的。首先代碼中處理的是 ??1px??? ,避免了直接操作小數(shù)像素的問題;當 ??dpr=2??? 時,換算成物理像素為 2px,此時去縮放 ??scale(0.5)???、當 ??dpr=3??? 時,換算成物理像素為 3px,此時縮放 ??scale(0.3)??? 后,手機均會默認使用最小物理像素 ??1px??? 來渲染。按照 CSS3 ??transform??? 的 ??scale?? 定義,邊框可以任意細,理論上可以實現(xiàn)任意細的縮放效果。
該方案的優(yōu)點在于,針對老項目使用縮放的形式可以快速實現(xiàn) ??1px?? 的效果。
需要注意的是,我們是在 ??1px?? 的基礎上進行縮放!
- 如果項目中使用了 rem 單位的話,此處的?
?1px?? 是不能用 rem 單位的,否則根據(jù) rem 換算后再進行縮放,會使得邊框變得更細。 - 如果項目中使用了?
?postcss-pxtorem??? 插件進行編譯的話,記得不要對??1px?? 進行編譯。配置文檔參考?postcss-pxtorem。
.ignore {
border: 1Px solid; // ignored
border-width: 2PX; // ignored }??Px??? or ??PX??? is ignored by ??postcss-pxtorem?? but still accepted by browsers。
2. 動態(tài) Viewport + REM 方式
第二種實現(xiàn)方案是采用動態(tài)設置 ??viewport + rem??? 布局,該方案其實是參考了阿里早期開源的一個移動端適配解決方案 ??flexible??? ,本文進行了一些改進。該方案不僅解決了移動端適配的問題,同時也較好的解決了 ??1px?? 的問題。
在理解它的實現(xiàn)原理之前,我們先來了解幾個關鍵概念 ??viewport視口??? 及 ??meta 標簽??? 及 頁面縮放 ??initial-sacle??。
視口
就是瀏覽器上(或者是一個 APP 中的 ??webview??? )用來顯示網(wǎng)頁的那部分區(qū)域,但 ??viewport?? 又不局限于瀏覽器可視區(qū)域的大小,它可能比瀏覽器的可視區(qū)域要大,也可能比瀏覽器的可視區(qū)域要小。
網(wǎng)上關于 ??viewport?? 的介紹比較經(jīng)典的就是 ?Peter-Paul Koch 的 ?A tale of two viewports 。它闡述了三種 ??viewport???,我們一般最常用的是 ??layout viewport??? (瀏覽器默認的 ??viewport??)。默認寬度大于瀏覽器可視區(qū)域的寬度,所以瀏覽器默認會出現(xiàn)橫向滾動條。
const clientWidth = document.documentElement.clientWidth || document.body.clientWidth
通過 meta 標簽設置
如果不設置 ??meta??? 標簽的話,由于 ??viewport??? 默認寬度是大于瀏覽器可視區(qū)域的,所以需要通過設置 ??viewport??? 的寬度等于屏幕寬 ??width=device-width?? 來避免出現(xiàn)橫向滾動條。
name="viewport"
content="
width=device-width, // 設置viewport的寬等于屏幕寬
initial-scale=1.0, // 初始縮放為1
maximum-scale=1.0,
user-scalable=no, // 不允許用戶手動縮放
viewport-fit=cover // 縮放以填充滿屏幕
"
>
- ??
?name??? 設置元數(shù)據(jù)的名稱,??content??? 設置元數(shù)據(jù)的值。??name??? 屬性值為??viewport?? 時,表示設置有關視口初始大小的提示,僅供移動端使用 - ?同時設置?
?width=device-width,initial-scale=1.0?? 是為了兼容 iOS 和 IE 瀏覽器
關于頁面縮放
??initial-scale??? 縮放值越大,當前 ??viewport?? 的寬度就會越小,反之亦然。
比如屏幕寬度是 ??320px??? 的話,如果我們設置 ??initial-scale=2??? ,此時 ??viewport??? 的寬度會變?yōu)橹挥???160px??? 了。這也好理解,放大了一倍嘛,就是原來 ??1px??? 的東西變成 ??2px??? 了,但是并不是把原來的 ??320px??? 變?yōu)???640px??? ,而是在實際寬度不變的情況下,??1px??? 變得跟原來的 ??2px?? 的長度一樣了。
所以縮放頁面的時候,實際上改變了 CSS 像素的大小,而數(shù)量不變。所以原來需要 ??320px??? 才能填滿的寬度現(xiàn)在只需要 ??160px?? 就做到了。
在開篇的表格中對
CSS 像素的定義是,不考慮縮放情況下,1個 CSS 像素等于1個設備獨立像素。頁面放大 200% 時,CSS
像素個數(shù)不變,大小變?yōu)槎?,相當于一個 CSS 像素在橫縱向上會覆蓋兩個設備獨立像素。瀏覽器窗口可容納的設備獨立像素數(shù)量是不變的,所以可視區(qū)域內(nèi)
CSS 像素數(shù)量變少。
Flexible 適配方案及問題
有了上面幾個概念,下面我們來說說 ??flexible?? 方案的實現(xiàn)原理及歷史遺留問題。
Flexible 的大致實現(xiàn)思路是,首先根據(jù) dpr 來動態(tài)修改 ??meta??? 標簽中 ??viewport??? 中的 ??initial-scale??? 的值,以此來動態(tài)改變 ??viewport??? 的大小;然后頁面上統(tǒng)一使用 rem 來布局,??viewport??? 寬度變化會動態(tài)影響 ??html??? 中的??font-size?? 值,以此來實現(xiàn)適配。
為什么不直接引用 flexible 庫來進行移動端適配呢?
因為 ??lib-flexible??? 這個庫目前基本被棄用,由于該方案誕生較早,官方也是認為 ??flexible??? 已經(jīng)完成了它的歷史使命,比如當時它只處理了 iOS 不同 dpr的場景,安卓設備下默認都設置為 ??dpr = 1?? 等,這是有問題的。有關于這方面的詳細使用可以閱讀早期整理的文章 ?使用Flexible實現(xiàn)手淘H5頁面的終端適配 。
在日常的業(yè)務場景中,雖然我們不會去使用 ??flexible??? 庫,但其實大多還是沿用 ??flexible?? 實現(xiàn)的原理來進行移動端適配,并從中進行了一些改進來達到適配的目的。
下面為簡單實現(xiàn)
name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1,
minimum-scale=1,maximum-scale=1,viewport-fit=cover"
/>
為什么頁面縮放比例 initial-scale 設置為 1 / dpr ?
這也是為什么前文大篇幅去闡述設備像素比 ??dpr??、縮放等概念了。
通過設置頁面縮放比例為 ??1/dpr???,可將 ??viewport??? 的寬度擴大 ??dpr??? 倍。還是以 iPhone6 手機為例,不進行頁面縮放時 ??viewport??? 寬度 ??375px???、??dpr=2???。由于 ??dpr?? 的存在使得一個 CSS 像素需要兩個物理像素來渲染。
當設置 ??initial-scale = 1 / dpr = 0.5??? 時,獲取到的 ??viewport??? 寬度 ??clientWidth = 750px??? ,被擴大了 ??dpr??? 倍,就正好是設備物理像素的寬度。簡單推導一下就是,當 ??scale=0.5??? 時,由于 ??viewport?? 內(nèi)可容納的 CSS 像素數(shù)量的增多,相當于一個設備獨立像素在橫縱向上會覆蓋兩個 CSS 像素,
CSS像素個數(shù) = 設備獨立像素個數(shù) / scale = ( 物理像素個數(shù) / dpr )/ scale
scale = 1 / dpr
// 所以
CSS像素個數(shù) = 物理像素個數(shù)
此時我們寫的 ??1px??? 其實正好是一個物理像素的大小,并且可以較好的畫出 ??1px??? 的邊框,從而提高顯示精度,從此我們就可以愉快地直接寫 ??1px??? 啦!同時這個方案也較好的解決了只使用 rem 進行布局時,出現(xiàn)計算后的各種 ??0.5px、0.55px?? 等問題。完美~
#實戰(zhàn)對比
下面為 border 的幾種實現(xiàn)方式在不同測試機的對比圖。
測試機型為 ??iPhone6???、??iPhone6Plus???、??iPhoneXR???、??HUAWEI P30???,以 ??750??? 的設計稿為例,設置 ??fontSize = clientWidth / 7.5 + 'px'??。
4.1 首先采用第一種解決方案即 縮放的形式
按鈕1:直接寫 ??1px??,根據(jù) dpr 計算可知,效果總是偏粗的。
按鈕2:rem 布局下,不改變 ??viewport??? 的縮放比,即 ??initial-scale= 1???,??fontSize??? 計算后范圍在 ??48px~55px??? 不等,??0.01rem??? 計算后 ??iPhone6??? 為 ??0.5px??? ,??dpr=2???,顯示效果較好;而 ??Huawei P30??? 的 ??0.01rem = 0.48px??,此時邊框已經(jīng)展示不清楚了??傮w來說效果展示偏細。
? 按鈕3:也是 rem 布局下,??initial-scale= 1??,使用了第一種解決方案, 縮放 0.5 后,總體效果展示較好。
4.2 采用第二種解決方案,即動態(tài)設置 布局方式的對比圖
設置 rem 布局下,
? 按鈕1:直接寫 ??1px??
? 按鈕4:使用 ??0.01rem???,不同機型 ??fontSize??? 在 ??100px~144px???之間,??0.01rem??? 計算后基本都大于等于 ??1px??。
分析下計算過程:
- ??
?iPhone6??? 屏幕寬??375px???,??dpr = 2???、??initial-scale= 0.5??? 時,??clientWidth??? 變?yōu)??750px???,根元素??fontSize = 100px???,那么??0.01rem??? 正好等于??1px??,并且大小和設計稿一致,展示效果理論上應該是最好的。 - ??
?Huawei P30??? 屏幕寬??360px???,??dpr = 3???、??initial-scale= 0.3??? 時,??clientWidth??? 變?yōu)??1080px???,根元素??fontSize = 144px???,那么??0.01rem = 1.44px???。其實這個時候我們實現(xiàn)的已經(jīng)大于??1px?? 了。相當于大于1個物理像素來渲染。
兩種方式效果展示都比較好,說明在此方案下,我們可以直接寫 ??1px??? 或者 ??0.01rem?? 都是可以的。
一些手機的屏幕分辨率整理
|
設備型號 |
屏幕分辨率/物理像素px |
設備像素比dpr |
獨立像素 /CSS像素 |
屏幕尺寸-inch |
像素密度ppi |
|
iPhone4 |
640x960 |
2 |
320x480 |
3.5 |
326 |
|
Iphone5s |
640x1136 |
2 |
320x568 |
4 |
326 |
|
Iphone6 |
750x1334 |
2 |
375x667 |
4.7 |
326 |
|
iphone6 Plus |
1080x1920 (1242x2208) |
3 |
414x736 |
5.5 |
401 |
|
iphoneX |
1125x2436 |
3 |
375x812 |
5.8 |
458 |
|
iphoneXR |
828x1792 |
2 |
414x896 |
6.1 |
326 |
|
iphoneXs Max |
1242x2688 |
3 |
414x896?? |
6.5 |
458 |
|
Huawei P30 |
1080x2340 |
3 |
360x780 |
6.1 |
422 |
|
Huawei mate30 |
1080x2340 |
3 |
360x780 |
6.62 |
388 |
總結
移動端適配主要就分為兩方面,一方面要適配不同機型的屏幕尺寸,一方面是對細節(jié)像素的處理過程。如果你在項目中直接寫了 ??1px?? ,由于 dpr 的存在展示導致渲染偏粗,其實是不符合設計稿的要求。
如果你使用了 rem 布局計算出了對應的小數(shù)值,不同手機又有明顯的兼容性問題。此時老項目的話整體修改 ??viewport??? 成本過高,可以采用第一種實現(xiàn)方案進行 ??1px??? 的處理;新項目的話可以采用動態(tài)設置 ??viewport?? 的方式,一鍵解決所有適配問題。
其實移動端對 ??1px?? 的渲染適配實現(xiàn)起來配置簡單、代碼簡短,能夠快速上手。但文本通過大量篇幅去闡述其原理,旨在能夠?qū)ζ湔嬲斫獠氐捉饣?,希望能夠?qū)Υ蠹矣兴鶐椭?/p>
分享文章:為什么會存在 1px 問題?怎么解決?
URL分享:http://fisionsoft.com.cn/article/dpicohg.html


咨詢
建站咨詢
