移動端1px問題解決方案

高清屏中1px線問題

在移動端web開發中,UI設計稿中設置邊框爲1像素,前端在開發過程當中若是出現border:1px,測試會發如今retina屏機型中,1px會比較粗,便是較經典的移動端1px像素問題。javascript

爲何高清屏下1px更寬

高清屏(retina屏)是指高dpr的設備,其物理像素的密度更大。又分爲有兩倍屏,三倍屏。css

dpr:物理像素/css像素html

普通屏1個css像素對應1個物理像素2倍屏中一個css像素對應4個物理像素;三倍屏中則是9個。前端

按照這樣的置換規則後一張相同的圖片在不一樣的設備上纔會顯示相同的大小。java

1px的線在高清屏下本應不須要作特殊處理。兩倍屏下會自動用兩排物理像素去展現‘1px’的細線,普通屏用一排物理像素去展現‘1px’的細線,他們應該看起來是相同的。可是,就像數學中的概念:線是沒有寬度的,點是沒有大小的。像素一樣是沒有大小的。webpack

兩倍屏的物理像素密度是普通屏的兩倍,並非每個物理像素是普通屏的1/4大小,而是物理像素的間距是普通屏間距的1/2。ios

用兩倍屏下用兩排像素去展現,天然會比普通屏中用一排像素去展現看起來更粗web

如何修正高清屏下的1px問題

要解決1px問題,本質就是讓高清屏用一個物理像素去展現一個css像素面試

能夠按照不一樣屏幕的dpr做出轉換,好比在2倍屏下將1px的細線寫成border:0.5px。但這種方法只在ios上支持,安卓上會顯示成被當成0px處理。瀏覽器

更通用的方案中,有svg僞類元素兩種。

SVG方案

這種方案本質上border並無變細,可是boder被一分爲二,靠內側的是透明的

關鍵的樣式代碼是css中的svg生成函數。

SVG即矢量圖,用xml標籤寫在html文件中。

經過postcss-write-svg這個postcss插件將css中svg函數生成的圖像處理成base64。這樣就能夠在css文件直接調用svg函數。

/* src/index.css */@svg custom-name {  width: 4px;   height: 4px;   @rect { fill: transparent; width: 100%; height: 100%; stroke-width: 1; stroke: var(--color, black);   }}.svg-retina-border { border: 1px solid; border-image: svg(custom-name param(--color green)) 1 repeat;}.normal-border { border: 1px solid green;}

處理事後的樣子

剩餘完整代碼

import './index.css'const root = document.getElementById('root')const div2 = document.createElement('div')div2.innerHTML = 'SVG-retina-border'div2.className = 'svg-retina-border'root.append(div2)root.append(document.createElement('br'))const div3 = document.createElement('div')div3.innerHTML = 'normal-border'div3.className = 'normal-border'root.append(div3)

<!-- src/index.html --><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="root"></div> </body></html>
// webpack.config.jsconst path = require('path')const HtmlPlugin = require('html-webpack-plugin')module.exports = { mode: 'development', entry: { entry1: './src/index.js'  },  output: { path: path.resolve(__dirname, 'dist'), filename: '[name].js'  },  module: { rules: [{ test: /\.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] }]  },  plugins: [ new HtmlPlugin({ template: './src/index.html'  })  ],  devServer: { contentBase: path.resolve(__dirname, 'dist'), host: '0.0.0.0',  port: 3005, compress: true, disableHostCheck: true  }}

SVG

分別直接用xml的svg標籤和css實現了兩個100px,邊框寬爲1的矩形。

高清屏下效果以下。

1598073606858
 <-- 視口大小-->  <svg xmlns="custom-namespace" width="100" height="100"> <rect  <--矩形大小--> width="100" height="100" fill="transparent" <--svg中全部的單位都是px--> stroke-width="1" stroke="black"  />  </svg>  <div style="width: 100px;height: 100px;border: 1px solid black;box-sizing: border-box;"></div>

stroke-widthborder同樣,都將矩形的邊設爲了1px,可是用svg實現的矩形邊框看起來卻更細。關鍵的地方是使用svg標記的視口大小和使用rect標記的矩形大小相同的

svg中沒有盒模型的概念,它的stroke畫線並不是對應css中的border更像是不佔空間的outline。由於不佔空間,它會以rect(矩形)的邊界爲中心畫線,一條線一半寬度在矩形內,一半在矩形外

而由於視口寬度正好等於矩形的大小,看到的線寬就只有一半了

(用svg畫一個100px大小+1px邊寬的方形)

(用css畫一個100px大小+1px邊框的方形border-box)

若是把矩形縮小一點,不佔滿視口,這時候看到的border是完整的,因此和沒處理過的1px同樣粗。

border-image

border-image是三個屬性的縮寫

border-image-source: url('https://misc.aotu.io/leeenx/border-image/box.png');border-image-slice: 33% 20% 3 fill;border-image-repeat: stretch;
  • border-image-source:圖片連接或base64;
  • border-image-slice:圖片切割的四個位置。把圖片切成9塊,除中間一塊,其餘八塊分別被當成邊框使用。接受1-4個參數(使用相似於padding/margin的尺寸設置)。能夠是百分比(相對於圖片自身),也能夠是數字(單位是px)。最後的fill決定中間那塊圖片會不會被當成background使用。
  • border-image-repeat:stretch/round(平鋪)/repeat(重複)上下左右四個正位的圖片怎樣被當成border使用。
  • round(平鋪)會壓縮,repeat(重複)會剪裁。

border-image必須配合border使用。最終border寬度是border-widthborder-style也必須指定,border-color能夠不用。

僞類元素方案

完整代碼

// index.html<!DOCTYPE html><html lang="en"> <head>  <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" type="text/css" href="./index.css" />  </head>  <body>  <div class="retina-border">retina border</div> <br />  <div class="normal-border">normal border</div>  </body></html>
// index.css.retina-border { position: relative;}.retina-border::before { content: '';  position: absolute; width: 100%; height: 100%;  transform-origin: left top; box-sizing: border-box; pointer-events: none;  border-width: 1px; border-style: solid; border-color: #333;}@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) { .retina-border::before {  width: 200%;  height: 200%;  transform: scale(0.5);  }}@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) { .retina-border::before {  width: 300%;  height: 300%;  transform: scale(0.33); }}.normal-border {  border: 1px solid #333;}

具體實現

以兩倍屏爲例

.retina-border { position: relative;}.retina-border::before {  content: '';  position: absolute; top: 0px;  right: 0px;  width: 200%; height: 200%; transform: scale(0.5); transform-origin: left top;  box-sizing: border-box; pointer-events: none;  border-width: 1px;  border-style: solid; border-color: #333;}

經過一個僞類選擇器在retinaborder元素中加了一個子元素

border-width: 1px將邊框的寬度設爲1px。

width:200%而後將僞類元素的寬高都設置成父元素的2倍。(可是邊框仍是1px)

transform:scale(0.5)僞類元素的x,y軸方向都縮放到0.5倍。

經過兩次尺寸的設置,使這個僞類子元素保持內容的大小仍是和父元素同樣,可是border:0.5px的效果。

pointer-events: none當有元素的層級重疊時,鼠標點擊是沒法穿透的。即絕對定位的僞類元素的層級更高,它底下的元素(即文字:retina border)沒法被事件觸發。置爲none時,絕對定位的元素不觸發事件,底下的那層才能被選中。

其餘css樣式做用

  • 僞類元素默認的display:inline。而position:absolute會使元素display:block。只有塊級元素的尺寸(寬/高)設置纔是有效的。

  • 其中僞類選擇器中content是必填項,否則沒法生效

  • transform-origin的縮放的中心點,默認是元素中心,

  • transform-origin的縮放的中心點,默認是元素中心,和絕對定位的top,right同樣,相對的是padding+content部分整個空間的位置

  • 絕對定位的元素其top和right值是相對於padding+content的,默認值是從content開始,因此要規定都是0,不然當父元素有padding時,border就移位了

(若是刪去position:absolute)

(若是刪去position:absolute+display:block)

當使用百分比時,其父元素的高度必須顯式指定,(20px/20view)不能是由子元素撐開的,可是寬度是能夠的。

兩種方案比較

兼容性

svg方案通過postcss處理,最終會影響瀏覽器兼容性的是border-image屬性

僞類元素元素:方案最終影響兼容性的是transform屬性

1598076296220

結論:svg方案的兼容性更好

靈活性

因爲svg只能畫出特定的形狀,因此沒法實現圓角邊框。而僞類元素方案能夠。

學習成本

svg方案所用到的border-image屬性、svg特性的理解成本較高,而且須要postcss-write-svg處理。僞類元素方案相較簡單。

總結

一般狀況,僞類元素方案更好,不管是從成本仍是靈活性出發。若是是爲了更高的兼容性選擇svg方案,border-image屬性必定要使用縮寫。(否則兼容性會更差兼容性測試)


關注我

我是山月,正致力於天天用五分鐘可以看完的簡短答案回答一個大廠高頻面試題。掃碼添加個人微信,備註進羣,加入高級前端進階羣.

歡迎關注公衆號【互聯網大廠招聘】,定時推送大廠內推信息及面試題簡答,天天學習五分鐘,半年進入大廠中

本文分享自微信公衆號 - 全棧成長之路(shanyue-road)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索