前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端进阶|在手机上画一条1px细线,为什么这么难?

前端进阶|在手机上画一条1px细线,为什么这么难?

作者头像
用户9899350
发布2022-07-29 20:34:14
9040
发布2022-07-29 20:34:14
举报
文章被收录于专栏:前端私教年年

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的矩形;高清屏下效果如下:

代码语言:javascript
复制
<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

代码语言:javascript
复制
// 定义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,在需要添加边框的元素之上加一个“蒙层”。

代码语言:javascript
复制
.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。

最后的效果如下:

代码语言:javascript
复制
<div class="retina-border">retina border</div>
<br />
<div class="normal-border">normal border</div>

该选哪种方案

两种方案的兼容性和灵活性对比如下:

  1. 兼容性

svg方案需要考虑border-image的兼容性,伪类元素方案需要考虑transform的兼容性。svg的兼容性更好。

  1. 灵活性

由于svg只能画出特定的形状,所以无法实现圆角边框。而伪类元素方案可以。伪类元素灵活性更好。

综合上述的考虑,我们的项目选择的是伪类元素方案,因为使用圆角边框的地方很多。而且从两种方案的篇幅不难看出来,这个方案的学习成本也低很多。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端私教年年 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1px问题由来
  • 如何修正1px问题
    • SVG方案
      • 实际操作
    • 伪类元素方案
      • 该选哪种方案
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档