前端进阶|为什么在手机上画1px的细线这么难?
1px问题出处在做手机项目的时候,有一个无法回避的问题:在手机上,1px的细线看起来会比较宽。事实上,这不仅仅是手机上会出现的问题。准确的说,这是高清屏的“通病”,在高清PC上也会出现。高清屏是指高dpr设备,dpr是指物理像素/css像素。此类设备具有更高密度的物理像素。又可细分为双屏和三屏。在普通屏幕上,仅使用1个物理像素呈现1个CSS像素;在2X屏幕上,1个CSS像素使用4个物理像素;在3X屏幕上,它使用9个物理像素。因为只有按照这样的映射关系,一张图片在不同的设备上才会显示出相同的大小。写到这里,似乎“为什么1px的线在高清屏下更宽”这个问题还没有说清楚。将高清屏下的像素映射关系代入1px线的场景,你会发现2x屏下的线宽是2个物理像素,3x屏下的线宽是3。有一个概念在数学:线没有宽度,点没有大小。像素也没有大小。2x屏的物理像素密度是普通屏的2倍,并不是说每个物理像素的大小是普通屏的1/4,而是物理像素间距是普通屏的1/2。2x屏使用两个显示一行像素自然会比在普通屏幕下使用一行像素看起来厚。如何解决1px问题解决1px问题,其实质就是让高清屏用一个物理像素显示一个css像素。最简单粗暴的方法:将1px的细线写成border:0.5pxunder2timesscreen。但是这个方法只在iOS上支持,在Android上会显示为0px。在更一般的方案中,有两种svg和伪类元素。SVG方案SVG是指矢量图形,具体在代码中,会作为一个xml标签组装在html文件中。我用svg和css实现了两个100px的矩形,边框宽度为1;高清屏下效果如下:
stroke-width和border-width一样,设置矩形的边宽为1px,但是用svg实现的矩形边框用来看看起来比较细。关键是svg标记的视口大小和rect标记的矩形大小是一样的。下面用一张更形象的图来说明:(使用svg的stroke-width绘制一个尺寸为100px+1px边宽的正方形)(使用css的border-width绘制一个尺寸为100px+1px的正方形sidewidth)svg中的stroke-width线不对应css中的border-width,更像是不占空间的轮廓。因为不占空间,所以会以图形边界为中心绘制一条线。一条线的一半宽度在矩形内部,一半在矩形外部。视口的大小刚好是矩形的大小,看到的线宽只有一半。为了证明,您可以稍微缩小绘制的矩形,使其不会填满视口。可以看到此时和未处理的1px一样粗。上面的实际操作是关于svg的基础知识,但是实际业务代码肯定不会直接这么用,因为代码的可扩展性太低了。通常使用postcss-write-svg插件,让我们直接在css文件中定义svg//definesvgfunction@svgcustom-name{width:4px;高度:4px;@rect{填充:透明;宽度:100%;高度:100%;笔划宽度:1;中风:var(--颜色,黑色);}}.svg-retina-border{border:1pxsolid;border-image:svg(custom-nameparam(--colorgreen))1repeat;}.normal-border{border:pxsolidgreen;}伪类元素方案该方案使用伪类element::before在需要添加边框的元素之上添加一个“遮罩层”。.target{位置:相对;}.target::before{宽度:200%;高度:200%;边框:1px实心#333;变换:比例(0.5);内容:'';位置:绝对;顶部:0px;右:0px;变换原点:左上;框大小:边框框;pointer-events:none;}以双屏为例,以上是demo代码,我们将遮罩层的宽高设置为目标2倍元素大小,边框宽度为1px,并且然后进行图形变换transform:scale(0.5),整体宽高为0.5倍。通过两次大小变换,遮罩层的大小与目标元素保持一致,但边框只有0.5px。最终效果如下:
retinaborder