移动端1px问题
  # 移动端1px问题
在移动端web开发中,UI设计稿中设置边框为1像素,前端在开发过程中如果出现border:1px,测试会发现在retina屏机型中,1px会比较粗,即经典的移动端1px像素问题。
1px 问题在实际面试中,尤其是大厂面试中出现的频率是比较高的。本文就探讨几种1px问题解决方案。
# 1px 问题的起因
1px 问题指的是在一些 Retina屏幕 的机型上,移动端页面的 1px 会变得很粗,呈现出不止 1px 的效果。
原因很简单——CSS 中的 1px 并不能和移动设备上的 1px 划等号。它们之间的比例关系有一个专门的属性来描述:
// 设备像素比
window.devicePixelRatio = 设备的物理像素 / CSS像素
一个物理像素等于多少个设备像素取决于移动设备的屏幕特性(是否是Retina)和用户缩放比例。
大家可以尝试打开自己的 Chrome 浏览器,启动移动端调试模式,然后尝试在控制台去输出这个 devicePixelRatio 的值。这里我选中了 iPhone6/7/8 这系列的机型,输出的结果就是2:
这就意味着我设置的 1px CSS 像素,在移动端上会用 2 个物理像素来进行渲染,所以实际看到的一定会比 1px 粗一些。
# 解决方案
1px 问题的解决方案是其实非常多的。不过从实用的角度出发,建议大家掌握3~4种就可以了,其他方法了解一下就行。
| 方案 | 优点 | 缺点 | 
|---|---|---|
| 直接写 0.5px | 代码简单 | IOS及Android老设备不支持 | 
| 用图片代替边框 | 全机型兼容 | 修改颜色及不支持圆角 | 
| background渐变 | 全机型兼容 | 代码多及不支持圆角 | 
| box-shadow模拟边框实现 | 全机型兼容 | 有边框和虚影无法实现 | 
| 伪元素先放大后缩小 | 简单实用 | 缺点不明显 | 
| 设置viewport解决问题 | 一套代码适用所有页面 | 缺点不明显 | 
# 1.直接写 0.5px
在 WWDC大会上,对ios8+的并且是DPR=2的设备来说,给出来了1px方案,当写 0.5px的时候,就会显示一个物理像素宽度的 border。 所以在iOS下,你可以这样写
border:0.5px solid #E5E5E5
虽然解决问题了,但是实用性不高,首先,得考虑IOS 系统需要8及以上的版本,安卓系统则有不兼容问题。
# 2.用图片代替边框
border: 1px solid transparent;
border-image: url('xxx.jpg') 2 repeat;
虽然解决问题了,但是后期样式调整会让人奔溃,比如颜色调整得UI小伙伴重新上传图片,然后又要修改代码,或者直接文件替换有涉及到图片缓存问题,如果后期来了一个要有边框圆角需求完全没法搞。
# 3.background渐变
background-position: left top;
background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0.5,transparent),color-stop(0.5,#e0e0e0),to(#e0e0e0));
代码多,展示的边框实际是在原本的border空间内部的,如果元素背景色有变化的样式, 边框线也会消失;最后也不能适应圆角样式。
# 4.box-shadow模拟边框实现
box-shadow: 0  -1px 1px -1px #e5e5e5,   //上边线
            1px  0  1px -1px #e5e5e5,   //右边线
            0  1px  1px -1px #e5e5e5,   //下边线
            -1px 0  1px -1px #e5e5e5;   //左边线
毕竟展示的阴影和边框一个样,但如果有边框还要有虚影样式就没法搞,鱼和熊掌不可兼得。
# 5.伪元素先放大后缩小
这个方法的可行性会更高,兼容性也更好。
实现方式:在目标元素的后面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸展开铺在目标元素上,然后把它的宽和高都设置为目标元素的两倍,border值设为 1px。接着借助 CSS 动画特效中的放缩能力,把整个伪元素缩小为原来的 50%。此时,伪元素的宽高刚好可以和原有的目标元素对齐,而 border 也缩小为了 1px 的二分之一,间接地实现了 0.5px 的效果。
.hairline{
  position: relative;
  &::after{
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    height: 1px;
    width: 100%;
    transform: scaleY(0.5);
    transform-origin: 0 0;
    background-color: #EDEDED;
  }
}
目前大部分移动端UI采用该方案,全机型兼容。
# 6.设置viewport解决问题
利用viewport+rem+js 实现的,边框1px直接写上自动转换。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
        <title>Document</title>
        <style type="text/css"></style>
    </head>
    <body>
        <script type="text/javascript">
            let viewport = document.querySelector('meta[name=viewport]')
            //下面是根据设备像素设置viewport
            if (window.devicePixelRatio == 1) {
                viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no')
            }
            if (window.devicePixelRatio == 2) {
                viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no')
            }
            if (window.devicePixelRatio == 3) {
                viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no')
            }
            function resize() {
                let width = screen.width > 750 ? '75px' : screen.width / 10 + 'px'
                document.getElementsByTagName('html')[0].style.fontSize = width
            }
            window.onresize = resize
        </script>
    </body>
</html>
这种方式,优点很明显,全机型兼容,直接写1px简单方便!
# 总结
新项目最好使用的是设置viewport解决问题,这个方法兼容性好,后期写起来方便,其次用的比较多的方法就是伪元素的方法。 其他的背景图片,阴影的方法毕竟还是不太灵活,而且兼容性不好。