物理像素

含义: 设备屏幕实际拥有的像素点,屏幕的基本单元,是有实体的。

 

比如iPhone 6的屏幕在宽度方向有750个物理像素点,高度方向有1334个物理像素点,所以iPhone 6 总共有750*1334个物理像素点。

逻辑像素

含义: 称为设备独立像素(Device Independent Pixel,DIP),也称为CSS像素CSS里定义像素点,比如1px表示逻辑像素为1

详解: 默认情况下1物理像素 = 1逻辑像素, 在高像素密度的设备上1物理像素 = 多个逻辑像素, 比如: 以iPhone6为例,设计稿给出一个图片的宽高为40*40,在实际开发中要除以2,宽高要写成20*20,因为从iPhone4开始, 苹果公司为其产品mac、iPhone以及iPad的屏幕配置了Retina高清屏,在Retian屏上,即 DPR===2。然后现在比较近的,比如iPhone12 mini的DPR是3。

 

设备像素比(Device Pixel Ratio,DPR)

含义: 设备的物理像素与逻辑像素的比

公式: 物理像素/逻辑像素

我们可以通过window.devicePixelRatio获取设备的dpr

 

用户缩放浏览器也会引起css中px的变化

  • 当用户把页面放大一倍,那么css中1px所代表的物理像素也会增加一倍;

  • 把页面缩小一倍,css中1px所代表的物理像素也会减少一倍;

即如果发生缩放或者放大,虽然默认情况下一个CSS像素应该是等于一个物理像素的宽度的,但是浏览器的放大操作让一个CSS像素等于了两个设备像素宽度(这时效果就和前面举例iphone中DPR为2一样,放大使得1个CSS的像素px这时变为了两倍)。 从上面的例子可以看出,CSS像素从来都只是一个相对值。

 

 

1px细线问题

在上文我们已经知道,CSS像素为1px宽的直线,对应的物理像素是不同的,在不同DPR下,可能是会有实际2倍或者3倍粗,而设计师想要的1px宽的直线,其实就是1物理像素宽,而不是设置1px后被转换成2-3个物理像素。

对于CSS而言,可以认为是border: 0.5px;这是多倍屏下能显示的最小单位。然而,并不是所有手机浏览器都能识别border: 0.5px,有的系统里,0.5px会被当成为0px处理,那么如何1px细线问题呢?

 

首先可以说一下怎么画一条1像素细线

直接写 0.5px,即通过缩放解决

  • 采用transform: scale()的方式,该方法用来定义元素的2D 缩放转换:

transform: scale(0.5,0.5);

注意scale(0.5,0.5)也可以写(0.2,0.2)画0.2的细线,实际上这个就是用的缩放,所以可以设置其他非0.5

  • 采用meta viewport的方式

<meta name="viewport" content="width=device-width, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5"/>

 

因此很多解决1px问题的话一个常用办法就是缩放

1.viewport 缩放

viewport + rem 实现

同时通过设置对应viewportrem基准值,这种方式就可以像以前一样轻松愉快的写1px了。
devicePixelRatio = 1,2,3 时,输出viewport

<html>
    <head>
        <title>1px question</title>
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
        <meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">        
        <style>
            html {
                font-size: 1px;
            }            
            * {
                padding: 0;
                margin: 0;
            }
            .top_b {
                border-bottom: 1px solid #E5E5E5;
            }
            
            .a,.b {
                        box-sizing: border-box;
                margin-top: 1rem;
                padding: 1rem;                
                font-size: 1.4rem;
            }
            
            .a {
                width: 100%;
            }
            
            .b {
                background: #f5f5f5;
                width: 100%;
            }
        </style>
        <script>
            var viewport = document.querySelector("meta[name=viewport]");
            //下面是根据设备像素设置viewport
            if (window.devicePixelRatio == 1) {
                viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no');
            }
            if (window.devicePixelRatio == 2) {
                viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
            }
            if (window.devicePixelRatio == 3) {
                viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no');
            }
            var docEl = document.documentElement;
            var fontsize = 32* (docEl.clientWidth / 750) + 'px';
            docEl.style.fontSize = fontsize;
        </script>
    </head>
    <body>
        <div class="top_b a">下面的底边宽度是虚拟1像素的</div>
        <div class="b">上面的边框宽度是虚拟1像素的</div>
    </body>
</html>

 

 

这种兼容方案相对比较完美,适合新的项目,老的项目修改成本过大。

优点:

  • 所有场景都能满足

  • 一套代码,可以兼容基本所有布局

缺点:

  • 老项目修改代价过大,只适用于新项目


2.伪元素+transform

构建1个伪元素, border1px, 再以transform缩放到50%

对于老项目,有没有什么办法能兼容1px的尴尬问题了,个人认为伪类+transform是比较完美的方法了。
原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并将 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。
单条border样式设置:

.setOnePx{
    position: relative;
    &::after{
      position: absolute;
      content: '';
      background-color: #e5e5e5;
      display: block;
      width: 100%;
      height: 1px; /*no*/
      transform: scale(1, 0.5);
      top: 0;
      left: 0;
    }
  }

可以看到,将伪元素设置相对定位,并且和父元素的左上角对其,将width 设置100%,height设置为1px,然后进行在Y方向缩小0.5倍。

 

四条boder样式设置:

 

.setBorderAll{
       position: relative;
         &:after{
             content:" ";
             position:absolute;
             top: 0;
             left: 0;
             width: 200%;
             height: 200%;
             transform: scale(0.5);
             transform-origin: left top;
             box-sizing: border-box;
             border: 1px solid #E5E5E5;
             border-radius: 4px;
        }
      }

同样为伪元素设置相对定位,并且和父元素左上角对其。将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍。

 

最好在使用前也判断一下,结合 JS 代码,判断是否 Retina 屏:

if(window.devicePixelRatio && devicePixelRatio >= 2){
  document.querySelector('ul').className = 'scale-1px';
}

优点:可以满足所有场景,且修改灵活。缺点:对于已使用伪类的元素(例如clearfix)要多层嵌套。

然后,还有许多其他办法,比如:使用box-shadow模拟边框,使用border-image,使用background-image,使用box-shadow模拟边框,这些效果都不太好,略。主要还是使用缩放这类比较有效。

 

参考:https://www.cnblogs.com/AhuntSun-blog/p/13581877.html

 https://juejin.cn/post/6844903797722972168