乐趣区

关于javascript:前端进阶|在手机上画一条1px细线为什么这么难

1px 问题由来

在做挪动端我的项目时,有一个逃不掉的问题:在手机上,1px 的细线会看起来更宽。
其实这不仅是手机上会呈现的问题,精确来说,这是高清屏的“通病”,在高清的 PC 上也会同样有。

高清屏是指高 dpr 的设施,dpr 指物理像素 /css 像素,这样的设施其物理像素的密度更大。又能够细分为两倍屏,三倍屏。

在一般屏,1 个 css 像素只用 1 个物理像素出现;2 倍屏中,1 个 css 像素会用 4 个物理像素;3 倍屏中则是 9 个。

因为只有依照这样的映射关系,一张图片在不同的设施上,才会显示雷同的大小。

写到这里,仿佛还没有讲清“为什么 1px 的线在高清屏下会更宽”这个问题。

将高清屏下的像素映射关系代入 1px 线的场景中,会发现:2 倍屏下的线宽是 2 个物理像素,3 倍屏下是 3 个。

数学中有个概念:线是没有宽度的,点是没有大小的。像素同样是没有大小的。

2 倍屏的物理像素密度是一般屏的两倍,并不是每一个物理像素大小是一般屏的 1 /4,而是物理像素的间距是一般屏间距的 1 /2。

2 倍屏下用两排像素去展现,天然会比一般屏下用一排像素去展现,看起来更粗。

如何修改 1px 问题

要解决 1px 问题,实质就是让高清屏用一个物理像素去展现一个 css 像素。

最简略粗犷的形式:在 2 倍屏下将 1px 的细线写成 border:0.5px。但这种办法只在 iOS 上反对,安卓上会显示成被当成0px 解决。

更通用的计划中,有 svg 和伪类元素两种。

SVG 计划

SVG 指的是矢量图,具体在代码中,会作为 xml 标签组装在 html 文件中。

我用 svg 和 css 两种形式别离实现了两个 100px,边框宽为 1 的矩形;高清屏下成果如下:

<svg xmlns="custom-namespace" width="100" height="100">
    <rect
        width="100"
        height="100"
        fill="transparent"
        // 宽度 1px
        stroke-width="1"
        stroke="black"
    />    
</svg>    
<div     
    style="
        width: 100px;
        height: 100px;
        // 宽度 1px
        border: 1px solid black;
        box-sizing: border-box;
    "
></div>

stroke-width 和 border-width 一样,将矩形的边宽设为了 1px,然而用 svg 实现的矩形边框看起来却更细。

要害的中央是,应用 svg 标记的 视口大小 和应用 rect 标记的 矩形大小 是雷同的。

上面用一个比拟形象的图来解释:


(用 svg 的 stroke-width 画一个 100px 大小 +1px 边宽的方形)


(用 css 的 border-width 画一个 100px 大小 +1px 边宽的方形)

svg 中的 stroke-width 画线并不是对应 css 中的 border-width,而更像是不占空间的 outline。

因为不占空间,它会以图形的边界为核心画线,一条线一半宽度在矩形内,一半在矩形外。而视口大小正好就是矩形的大小,看到的线宽就只有一半了。

为了佐证,能够把画的矩形放大一点,不占满视口,能够看出,这时候和没有解决过的 1px 一样粗了。

实际操作

以上是对于 svg 的基础知识,但理论的业务代码必定不会间接这样应用,因为代码的可扩展性太低。

通常会应用 postcss-write-svg 这个插件,让咱们间接在 css 文件定义 svg

// 定义 svg 函数
@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: px solid green;}

伪类元素计划

这种计划借助伪类元素::before,在须要增加边框的元素之上加一个“蒙层”。

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

以二倍屏为例,上述是 Demo 代码,咱们将蒙层的宽高都设置为指标元素的 2 倍,边框宽度是 1px,而后将它进行图形变换transform: scale(0.5),整体宽高为 0.5 倍。

通过两次尺寸的变换,这个蒙层的大小和指标元素保持一致,然而 border 只有 0.5px。

最初的成果如下:

<div class="retina-border">retina border</div>
<br />
<div class="normal-border">normal border</div>

该选哪种计划

两种计划的兼容性和灵活性比照如下:

  1. 兼容性

svg 计划须要思考 border-image 的兼容性,伪类元素计划须要思考 transform 的兼容性。svg 的兼容性更好。

  1. 灵活性

因为 svg 只能画出特定的形态,所以无奈实现圆角边框。而伪类元素计划能够。伪类元素灵活性更好。

综合上述的思考,咱们的项目选择的是伪类元素计划,因为应用圆角边框的中央很多。而且从两种计划的篇幅不难看出来,这个计划的学习老本也低很多。

退出移动版