TomIsion/blog-backup

移动端的 1px 问题

Opened this issue · 0 comments

总结、记录下 iOS 移动端 1px 的问题

产生的原因

devicePixelRatio

devicePixelRatio in MDN

这里有两个概念:

  • 物理像素
  • CSS像素

物理像素其实就是买手机的时候,手机参数中的一项,以下面这个 iPhone X 的参数为例:

WX20180320-110923

CSS 像素就是页面布局的容器尺寸

这里 devicePixelRatio 就是 物理像素 / CSS像素 的值

换句话来说,就是布局中的一个像素点对于物理像素中多少个像素点来显示

iPhone 4 的时代,屏幕还没有引入视网膜屏,此时的 devicePixelRatio 的值还是 1

随着视网膜屏以及HiDPI屏的出现普及,devicePixelRatio 的值从 1 到 4 甚至到了 9

也就是编写网页 1px 的元素,在手机屏幕上可能是以 3 * 3 的像素点在进行渲染,会让图片更加清晰

所以这个属性本质上是描述的:

  • 当前显示设备的物理分辨率与CSS像素分辨率的比值
  • CSS像素的大小相对物理像素大小的比值

1px 的问题

所以为了在移动端得到最佳的效果,一般设计稿都是按照移动端设备的物理分辨率来设计的

很多的边框都会被设计成 1px 的宽度,但是在还原设计稿的时候,CSSpx 最小单位通常来说都是 1px

.border-bottom {
    border-bottom: 1px solid #000;
}

随之真机上因为 视网膜屏或HiDPI屏 的原因,就会显示 2px 或者是 3px 的宽度,和期待的效果出入很大

解决问题

了解了出现的原因,下面就看看如何解决这个问题

0.5px

其实第一反应就是能不能出现比 1px 小情况,譬如 CSS 中编写 0.5px ,那么在 devicePixelRatio = 2 的屏幕上真实渲染物理像素就是 1px,这样就可以达到还原设计稿的效果

但是 0.5px 这个属性值只有 Safari+和Firefox 支持

可以在 MacSafari 上测试下面的代码:

.common-border {
    border-bottom: 1px solid #000;
}

.half-border {
    border-bottom: 0.5px solid #000;
}

可以看到明显的粗细不同

但是这个方法不具备普适性,只有在特定的浏览器中才会有效果,一般需要配合浏览器检测使用

if (window.devicePixelRatio && devicePixelRatio >= 2) {
  const ele = document.createElement('div')
  ele.style.border = '.5px solid transparent'
  document.body.appendChild(ele)
  if (ele.offsetHeight === 1)
  {
    document.querySelector('html').classList.add('half')
  }
  document.body.removeChild(ele)
}

border-image

这个是之前部门广泛推广的一种方式,原理就是借助 border-image-slice 来实现

本质上就是借助图片边框(图片一般如下图所示),然后其中截取的内容是一半白色一半边框色的内容,最终物理像素渲染的就是真实的物理 1px

.border-1px {
    border-width: 1px;
    border-image: url(border.png) 2 repeat;
}

border.png 如下图,是 6 * 6 的边框图

borde

通常实践中因为边框图片尺寸都比较小的原因,都会在打包过程中使用 base64 编码来节省网络请求

借助 border-image 实现的主要问题在于:如果修改边框的颜色,则需要修改对应的边框图片颜色,在开发过程中也不是普遍的解决方案

但是在熟悉 border-image 样式的过程中,还是了解了很多有趣的点,具体可以参考下面张鑫旭大神的文章

参考资料

伪元素配合 transform 缩放

之前我们提到过 0.5px 这种解决方案,类似的,按比例缩小也是一种解决方案

缩小自然就是使用

transfrom: scale(0.5);
transform-origin: left top;

越来越多的现代前端组件库中会使用这种方式来兼容 1px 的问题,尤其是借助各种CSS预处理语言,可以方便的实现边框颜色自定义,支持圆角这些特性

譬如使用 Less 这样实现颜色的自定义(摘自 Vux - 1px 中的实现)

.setLine(@c: #C7C7C7) {
  content: " ";
  position: absolute;
  left: 0;
  top: 0;
  width: 200%;
  height: 200%;
  border: 1px solid @c;
  color: @c;
  transform-origin: left top;
  transform: scale(0.5);
}

.vux-1px, .vux-1px-t, .vux-1px-b, .vux-1px-tb, .vux-1px-l, .vux-1px-r {
  position: relative;
}

.vux-1px {
  &:before {
    /*
    ** 这里可以自定义配置颜色
    ** .setLine(#000);
    */
    .setLine();
  }
}

在此基础上,我们可以写出一版支持边框圆角的

.setLine(@c: #C7C7C7, @r: 0) {
  content: " ";
  position: absolute;
  left: 0;
  top: 0;
  width: 200%;
  height: 200%;
  border: 1px solid @c;
  border-radius: @r;
  transform-origin: left top;
  transform: scale(0.5);
}

.vux-1px {
  position: relative;
}

.vux-1px {
  &:before {
    /*
    ** 这里增加支持自定义边框圆角
    ** .setLine(#000, 2px);
    */
    .setLine();
  }
}

相比之下,这种解决方案需要兼容 devicePixelRatio = 3 的情况也是比较简单的

都可以通过CSS预处理语言来进行配置

奇技淫巧

关注下手淘首页是如何解决 1px 边框问题的

手淘首页是使用 rem 作为页面布局单位的,但是其中涉及到 1px 的地方却是使用元素来模拟的

WX20180322-165116@2x-w375

对应的样式如下

element.style {
  box-sizing: border-box;
  line-height: 0;
  background-color: rgb(232, 232, 232);
  width: 10rem;
  height: 1px;
}

为什么这里是 height: 1px;

我们回到页面顶部关注这样一个 meta 标签(模拟器是 iPhone X 环境):

<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">

当前页面都被缩放 1/3 显示,那么换句话说,就是 height: 1px 被压缩了 1/3

最后映射到物理像素上,也就完美的呈现了 1px 的内容

当然,因为设置了这个 <meta> 标签,所有的其他元素的大小自然也要对应的放大,不过借助 rem,只需要改变根节点的 font-size 就行

综合来看,还是手淘首页这种解决方案最符合解决 1px 问题的初衷

为了追求完美的前端呈现效果,还是要不断综合不断尝试

Todos

  • canvas 相关的问题