css篇(001)— 移动端1px像素问题及解决方法

903 阅读7分钟

前言

在移动端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)宽高比(近似)比例(近似)
3GS3.5 inch163 ppi320 * 480 pt320 * 480 px@1x1.52:3
4/4S3.5 inch326 ppi320 * 480 pt640 * 960 px@2x1.52:3
5/5S/5c/5E4.0 inch326 ppi320 * 568pt640 * 1136 px@2x1.779:16
6/6S/7/84.7 inch326 ppi375 * 667 pt750 * 1134 px@2x1.779:16
6+/6S+/7+/8+5.5 inch401 ppi414 * 736 pt1242 *2208 px (1080x1920)@3x1.779:16
X5.8 inch458 ppi375 * 812 pt1125 * 2436 px@3x2.169:20
XS5.8 inch458 ppi375 * 812 pt1125 * 2436 px@3x2.169:20
XS Max6.5 inch458 ppi414 * 896 pt1242 * 2688 px@3x2.169:20
XR6.1 inch326 ppi414 * 896 pt828 * 1792 px@2x2.169: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+伪类实现。