前言

由于移动端屏幕的像素密度都非常高,css中的1px呈现在屏幕中,往往不止1个物理像素,看起来比较粗,设计师很难满意。这和devicePixelRatio有关。
transform实现看起来比较细的边框是一个很简单的方案,在移动端兼容性也非常好,不需要多余的资源,当然,肯定要多些几行代码。

实现

实现一条横向的分隔线,一般这样写:

<!-- HTML -->
<div></div>

/* CSS */
div {
    position: relative;
}

div::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    border-bottom: 1px solid blue;
    transform: scaleY(.5);
}

其中的关键就是transform: scaleY(.5);,一条水平方向,高度是1px(不是物理像素)的线,由于Y方向压缩了一半,当然就变细了。如果要实现的是竖线,就使用scaleX缩放X方向。

如何实现边框呢?可以把before或者after伪元素先设置为父元素的200%,然后再缩小为原来的一半。代码如下:

<!-- HTML -->
<div></div>

/* CSS */
div {
    position: relative;
}

div::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    height: 200%;
    width: 200%;
    border: 1px solid blue;
    transform-origin: 0 0;
    transform: scaleY(.5);
}

问题

如果没有背景颜色,效果看起来很完美(上面代码省略了无关紧要的内容):
无背景色

但如果你想给按钮添加一个hover样式,在按下去的时候,添加一个背景颜色:
div:active {
background-color: blue;
}

这时就会发现,边框有点偏移:
有背景色,边框偏移

为啥呢?因为边框也占用空间,给伪元素设置了宽高后,伪元素的宽高是它的宽高+边框的宽度,在缩放了一半之后,宽高并不等于父元素的宽高的一半。咋解决呢?如下:
div::after{
...
transform: translate(-.5px, -.5px) scaleY(.5);
...
}

因为scale先于translate执行,所以偏移量只有1px缩放一半后的0.5px。(transform的执行顺序,请搜索transform: matrix,了解矩阵) 问题完美解决:
岁月静好

更简单的方法是使用box-sizing
div::after{
...
box-sizing: border-box;
...
}
效果和上面一样,稳稳的,很贴心。

注意点

使用伪元素作为边框,尤其是after伪元素,可能会遮盖整个父元素,如果父元素的正常子元素有click等鼠标事件,要设置正确的z-index

如果有疑问或者问题请留言或联系。

标签: 1px边框实现, 1px边框解决方案, transform scale