前言
在移动端web开发过程中,UI设计稿经常出现1px边框,前端工程师在实现的过程中通过border: 1px; 来实现该功能,测试时会发现在有些机型上这个1px边框显示是加粗的效果,这就是移动端1px像素问题。
基本概念
-
物理像素(设备像素):指设备能控制显示的最小物理单位,意指显示器上一个个的点。移动设备出厂时,不同设备自带的像素点就固定不变了,和屏幕尺寸有关。
-
虚拟像素(css像素):指的是css样式中代码使用的逻辑像素,在css规范中,长度单位可以分为两类,绝对单位以及相对单位。px 是一个相对单位,相对的是设备像素。
-
设备独立像素 (逻辑像素):可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: CSS 像素),这个点是没有固定大小的,越小越清晰,然后由相关系统转换为物理像素。
-
设备像素比(DPR): 设备像素比 = 设备像素 / 设备独立像素
也就是说,当逻辑像素是 1pt 时,在 DPR 为 2 的 设备上显示为 2px 的物理像素
如iPhone6 的dpr为2,物理像素750(x轴),则它的逻辑像素为375。也就是说,1个逻辑像素,在x轴和y轴方向,需要2个物理像素来显示,即:dpr=2时,表示1个CSS像素由4个物理像素点组成,如下图:
参考数据
各种类型的 iphone 手机屏幕设备的参数
| 手机型号 | 屏幕尺寸(inch) | 像素密度(PPI) | 逻辑分辨率(point) | 物理分辨率(屏幕分辨率)(Pixel) | 缩放因子(scale factor) | 宽高比(近似) | 比例(近似) |
|---|---|---|---|---|---|---|---|
| 3GS | 3.5 inch | 163 ppi | 320 * 480 pt | 320 * 480 px | @1x | 1.5 | 2:3 |
| 4/4S | 3.5 inch | 326 ppi | 320 * 480 pt | 640 * 960 px | @2x | 1.5 | 2:3 |
| 5/5S/5c/5E | 4.0 inch | 326 ppi | 320 * 568pt | 640 * 1136 px | @2x | 1.77 | 9:16 |
| 6/6S/7/8 | 4.7 inch | 326 ppi | 375 * 667 pt | 750 * 1134 px | @2x | 1.77 | 9:16 |
| 6+/6S+/7+/8+ | 5.5 inch | 401 ppi | 414 * 736 pt | 1242 *2208 px (1080x1920) | @3x | 1.77 | 9:16 |
| X | 5.8 inch | 458 ppi | 375 * 812 pt | 1125 * 2436 px | @3x | 2.16 | 9:20 |
| XS | 5.8 inch | 458 ppi | 375 * 812 pt | 1125 * 2436 px | @3x | 2.16 | 9:20 |
| XS Max | 6.5 inch | 458 ppi | 414 * 896 pt | 1242 * 2688 px | @3x | 2.16 | 9:20 |
| XR | 6.1 inch | 326 ppi | 414 * 896 pt | 828 * 1792 px | @2x | 2.16 | 9:20 |
| 注:这里的缩放因子呢,就是 DPR 的值 |
解决方案
1. 伪类+transform
利用:before或者:after重做border,并 transform的scale缩小一半,原先的元素相对定位,新做的border绝对定位。伪元素::after或::before是独立于当前元素,可以单独对其缩放而不影响元素本身的缩放。
伪元素大多数浏览器默认单引号也可以使用,和伪类一样形式,而且单引号兼容性(ie)更好些
/*手机端实现真正的一像素边框*/
.border-1px, .border-bottom-1px, .border-top-1px, .border-left-1px, .border-right-1px {
position: relative;
}
/*线条颜色 黑色*/
.border-1px::after, .border-bottom-1px::after, .border-top-1px::after, .border-left-1px::after, .border-right-1px::after {
background-color: #000;
}
/*底边边框一像素*/
.border-bottom-1px::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px;
transform-origin: 0 0;
}
/*上边边框一像素*/
.border-top-1px::after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
transform-origin: 0 0;
}
/*左边边框一像素*/
.border-left-1px::after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 1px;
height: 100%;
transform-origin: 0 0;
}
/*右边边框1像素*/
.border-right-1px::after {
content: "";
box-sizing: border-box;
position: absolute;
right: 0;
top: 0;
width: 1px;
height: 100%;
transform-origin: 0 0;
}
/*边框一像素*/
.border-1px::after {
content: "";
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border: 1px solid gray;
}
/*设备像素比*/
/*显示屏最小dpr为2*/
@media (-webkit-min-device-pixel-ratio: 2) {
.border-bottom-1px::after, .border-top-1px::after {
transform: scaleY(0.5);
}
.border-left-1px::after, .border-right-1px::after {
transform: scaleX(0.5);
}
.border-1px::after {
width: 200%;
height: 200%;
transform: scale(0.5);
transform-origin: 0 0;
}
}
/*设备像素比*/
@media (-webkit-min-device-pixel-ratio: 3) {
.border-bottom-1px::after, .border-top-1px::after {
transform: scaleY(0.333);
}
.border-left-1px::after, .border-right-1px::after {
transform: scaleX(0.333);
}
.border-1px::after {
width: 300%;
height: 300%;
transform: scale(0.333);
transform-origin: 0 0;
}
}
/*需要注意<input type="button">是没有:before, :after伪元素的*/
优点: 所有场景都能满足,支持圆角(伪类和本体类都需要加border-radius)。
缺点: 代码量大,对于已经使用伪类的元素(例如clearfix),可能需要多层嵌套;按钮的active样式不好书写。
2. 使用图片
使用border-image或者background-image,首先准备一张符合要求的图片 border-image样式设置:
.border-image-1px {
border-width: 0 0 1px 0;
border-image: url(./img/line-border.png) 0 0 2 0 stretch;
}
background-image样式设置:
.background-image-1px {
background: url(./img/line-border.png) repeat-x left bottom;
background-size: 100% 1px;
}
优点: 可以设置单条,多条边框,没有性能瓶颈的问题
缺点: 修改颜色麻烦,需要UI出图替换图片,圆角需要特殊处理,边缘会模糊。
3. viewport + rem
使用 viewport 和 rem,js 动态改变 viewport 中 scale 缩放。
在devicePixelRatio = 2 时,设置meta:
<meta name="viewport" content="width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
在devicePixelRatio = 3 时,设置meta:
<meta name="viewport" content="width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">
实例验证:
<!DOCTYPE html>
<html lang="en">
<head>
<title>移动端1px问题</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta name="viewport" id="WebViewport"
content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<style>
html {
font-size: 11px;
}
body {
padding: 1rem;
}
* {
padding: 0;
margin: 0;
}
.item {
padding: 1rem;
border-bottom: 1px solid gray;
font-size: 1.2rem;
}
</style>
<script>
var viewport = document.querySelector("meta[name=viewport]");
var dpr = window.devicePixelRatio || 1;
var scale = 1 / dpr;
//下面是根据设备dpr设置viewport
viewport.setAttribute("content", `width=device-width,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale},user-scalable=no`)
var docEl = document.documentElement;
var fontsize = 10 * (docEl.clientWidth / 320) + "px";
docEl.style.fontSize = fontsize;
</script>
</head>
<body>
<div class="item">border-bottom: 1px solid gray;</div>
<div class="item">border-bottom: 1px solid gray;</div>
</body>
</html>
4. 媒体查询利用设备像素比缩放,设置小数像素
IOS8+下已经支持带小数的px值,media query 对应 devicePixelRatio 有个查询值 -webkit-min-device-pixel-ratio; 样式如下:
/*这是css方式*/
.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
.border { border: 0.5px solid #999 }
}
/*ios dpr=2和dpr=3情况下border相差无几,下面代码可以省略*/
@media screen and (-webkit-min-device-pixel-ratio: 3) {
.border { border: 0.333333px solid #999 }
}
优点: 简单,好理解,不需要过多代码。
缺点: 兼容性差,目前只有IOS8+才支持,在IOS7及其以下、安卓系统都是显示0px。
5. box-shadow
利用阴影也可以实现
代码如下:
div {
-webkit-box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5);
}
优点: 没有圆角问题,简单。
缺点: 颜色不好控制。
6. postcss-write-svg
借助于PostCSS的插件postcss-write-svg来帮助我们。如果你的项目中已经有使用PostCSS,那么只需要在项目中安装这个插件。然后在你的代码中使用:
@svg 1px-border {
height: 2px;
@rect {
fill: var(--color, black);
width: 100%;
height: 50%;
}
}
.example {
border: 1px solid transparent;
border-image: svg(1px-border param(--color #00b1ff)) 2 2 stretch;
}
这样PostCSS会自动帮你把CSS编译出来:
.example {
border: 1px solid transparent;
border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E")
2 2 stretch;
}
优点: 简单易用。
缺点: 需要使用插件,缺点在于不适用于已有的项目。
总结
总结 通过该文,你大概了解了移动端1px的来龙去脉了,也明白了如何解决相关问题,如果这篇文章能解决你的疑问或者工作中问题,不妨点个赞收藏下。由于技术水平有限,文章中如有错误地方,请在评论区指出,感谢!
- 0.5px,相信浏览器肯定是会慢慢支持的,目前而言,如果能用的话,可以hack一下。
- 对于老项目,建议采用transform+伪类。
- 新项目可以设置viewport的scale值,这个方法兼容性好。
- postcss-write-svg简单易用,仅适合直线,圆角建议用transform+伪类实现。