本文主要介绍造成 1px 边框问题原因和几种常用的解决方案。
几个概念
像素(px)是作为网页布局的基础,通常一个像素就是计算机能够显示一种特定颜色的最小区域。当设备尺寸相同,像素变得越密集,屏幕能显示的画面就越细致。
像素是一个抽象的单位,它不是一个确定的物理量,也不是一个具体的点或者小方块(尽管可以用点和小方块来呈现),在不同的设备或不同的环境中,1px 所代表的大小是不同的。
以下先介绍几个基本的概念:
CSS 像素
CSS 像素是一个抽象的单位,主要使用在浏览器上,用来精确度量 Web 页面上的内容。一般情况之下,CSS 像素也被称为与设备无关的像素(device-independent pixel,简称 DIPs)或逻辑像素。
CSS 像素,顾名思义就是我们写 CSS 时所用的像素(当然 JavaScript 中也可以使用),它跟设备屏幕的物理像素没必然关系,比如 windows 的桌面显示器,当你修改显示器的分辨率,由 1920 的分辨率修改为 1280 分辨率,你会发现网页里的图形和字体会变大,同样的显示器,原来能显示网页的全部宽度,修改分辨率后只能显示部分宽度,也就是说 CSS 像素变大了。所以,CSS 像素是与设备无关的,可以被硬件和软件调节的单位。
物理像素(physical pixel)
物理像素又称为设备像素(device pixel)、设备物理像素,它是显示设备(如电脑显示屏、手机屏幕)最小的物理显示单位,每个物理像素由颜色值和亮度值组成。
一个设备的物理像素是固定不变的,我们经常听说的一倍屏、二倍屏(Retina)指的是设备以多少物理像素来显示一个 CSS 像素,多倍屏会以更多更精细的物理像素点来显示一个 CSS 像素点。例如,普通屏幕下一个 CSS 像素点对应一个物理像素点,而 Retina 屏中的一个 CSS 像素点对应四个物理像素点:

设备像素比(device pixel ratio)
设备像素比简称 dpr,定义了物理像素与设备独立像素之间的对应关系,其换算公式为:
1
设备像素比 = 物理像素 / 设备独立像素
浏览器中可以使用 window.devicePixelRatio 来获取设备像素比的值。
原因
啥玩应啊?
像素还分物理像素、逻辑像素,大家统一一点不好吗?
其实在很久以前的确是没区别的,CSS 里写个 1px,屏幕就给你渲染成 1 个实际的像素点,dpr = 1,多么简单,多么方便,多么自然。
然而,就有人出来搞事情了,这就是苹果的 Retina 技术。Retina 使用 4 个及至更多的物理像素点来渲染 1 个逻辑像素点,如此一来,同样的 CSS 代码设置的尺寸,在非 Retina 屏与 Retina 屏上显示的大小是一致的,但在 Retina 屏上却获得了更精细的显示效果。
在 Retina 屏上,通常 dpr 都不再是 1,而是大于 1,比如 iPhone 5/SE/6/7/8 的 dpr 为 2;iPhone 6/7/8 Plus 和 iPhone X 的 dpr 为 3;甚至有些 Android 机的 dpr 为非整数,比如 Pixel 2 的 dpr 为 2.625。
带来的问题
在苹果的带动下,Retina 技术在移动设备上已成为标配,那对于前端攻城狮们来说,就必须面对这样的事实:你想要画个 1px 的边框,但屏幕塞给你一条宽度为 2 或 3 物理像素的线,而设计师想要的其实就是 1 物理像素宽的线,你还不能像原生 Android 或 iOS 同事那样直接操纵物理像素点,这就是 1px 边框问题的由来。
如果 dpr 为 2,可以认为 CSS 设置 border: 0.5px; 就是 1 物理像素宽度的边框,但并不是所有移动设备的浏览器都能正确识别 border: 0.5px;,在有的系统里 0.5px 直接被处理为 0px 了,那如何来解决 1px 边框问题呢?
解决方案
border-image
首先准备一线符合应用要求的图片,然后根据 CSS 的 border-image 属性来设置。例如设置下边框:
1
2
3
4
.border-image-1px {
border-bottom: 1px solid transparent;
border-image: url(images/border.png) 0 0 2 0 stretch;
}
使用的图片是 2px 高,上部的 1px 颜色为透明,下部的 1px 使用视觉规定的 border 颜色,如图所示:

优点:
- 可以设置单条边框、多条边框;
- 没有性能瓶颈问题。
缺点:
- 修改边框颜色和样式比较麻烦;
- 某些设备边缘会模糊。
background-image
使用 background-image 与 border-image 的方法类似,需要先准备一张符合应用要求的图片,然后将边框的设置使用背景的方式来实现,例如:
1
2
3
4
.background-image-1px {
background: url(images/border.png) repeat-x left bottom;
background-size: 100% 1px;
}
优缺点与 border-image 一致。
box-shadow
使用 box-shadow 模拟边框,利用 CSS 对阴影处理的方式实现 1px 的效果,例如:
1
2
3
.box-shadow-1px {
box-shadow: inset 0px -1px 1px -1px #666;
}
优点:
- 代码量少,兼容性较好。
缺点:
- 边框有阴影,颜色变淡。
viewport + rem 实现
通过设置 viewport 的 rem 基准值,这种方式就可以像以前一样轻松愉快的写 1px 了。
可以结合 JavaScript 判断,当 dpr = 2 时:
1
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
当 dpr = 3 时:
1
<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">
优点:
- 一套代码,基本可以兼容所有布局。
缺点:
- 老旧项目修改代价太大。
伪类 + transform 实现
个人认为伪类 + transform 是比较完美的方法,原理是把元素的 border 去掉,然后利用 :before 或者 :after 重做 border,并将 transform 的 scale 缩小一半,将原先的元素相对定位,新做的 border 绝对定位即可。
单边框 border 样式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.border-1px{
position: relative;
border: none;
}
.border-1px:after{
content: '';
position: absolute;
bottom: 0;
background: #000;
width: 100%;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
如果需要四边 border 样式设置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.border-1px{
position: relative;
border: none;
}
.border-1px:after{
content: '';
position: absolute;
top: 0;
left: 0;
border: 1px solid #000;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 200%;
height: 200%;
-webkit-transform: scale(0.5);
transform: scale(0.5);
-webkit-transform-origin: left top;
transform-origin: left top;
}
其它方案
WWDC 对 iOS 系统给出的方案:
在 2014 年的 WWDC 大会上,对 iOS8+ 的并且是 dpr = 2 的设备来说,给出来了 1px 方案:当写成 0.5px 的时候,就会显示一个物理像素宽度的 border, 所以在 iOS 下,你可以这样写:
1
2
3
.box {
border:0.5px solid #ccc;
}
但这种解决方案通常 Android 不适用。
多背景渐变:
与 background-image 方案类似,只是将图片替换为 css3 渐变的方式:设置 1px 的渐变背景,50% 有颜色,50% 透明。样式设置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.background-gradient-1px {
background:
linear-gradient(#000, #000 100%, transparent 100%) left / 1px 100% no-repeat,
linear-gradient(#000, #000 100%, transparent 100%) right / 1px 100% no-repeat,
linear-gradient(#000,#000 100%, transparent 100%) top / 100% 1px no-repeat,
linear-gradient(#000,#000 100%, transparent 100%) bottom / 100% 1px no-repeat
}
/* 或者 */
.background-gradient-1px{
background:
-webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) left / 1px 100% no-repeat,
-webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) right / 1px 100% no-repeat,
-webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) top / 100% 1px no-repeat,
-webkit-gradient(linear, left top, right bottom, color-stop(0, transparent), color-stop(0, #000), to(#000)) bottom / 100% 1px no-repeat
}
但这种方式设置代码量大,也存在兼容问题。