移动端的 1px 问题
Opened this issue · 0 comments
总结、记录下 iOS
移动端 1px
的问题
产生的原因
devicePixelRatio
这里有两个概念:
- 物理像素
- CSS像素
物理像素其实就是买手机的时候,手机参数中的一项,以下面这个 iPhone X 的参数为例:
CSS 像素就是页面布局的容器尺寸
这里 devicePixelRatio
就是 物理像素 / CSS像素 的值
换句话来说,就是布局中的一个像素点对于物理像素中多少个像素点来显示
在 iPhone 4
的时代,屏幕还没有引入视网膜屏,此时的 devicePixelRatio
的值还是 1
随着视网膜屏以及HiDPI屏的出现普及,devicePixelRatio 的值从 1 到 4 甚至到了 9
也就是编写网页 1px 的元素,在手机屏幕上可能是以 3 * 3 的像素点在进行渲染,会让图片更加清晰
所以这个属性本质上是描述的:
- 当前显示设备的物理分辨率与CSS像素分辨率的比值
- CSS像素的大小相对物理像素大小的比值
1px 的问题
所以为了在移动端得到最佳的效果,一般设计稿都是按照移动端设备的物理分辨率来设计的
很多的边框都会被设计成 1px
的宽度,但是在还原设计稿的时候,CSS
中 px
最小单位通常来说都是 1px
.border-bottom {
border-bottom: 1px solid #000;
}
随之真机上因为 视网膜屏或HiDPI屏 的原因,就会显示 2px
或者是 3px
的宽度,和期待的效果出入很大
解决问题
了解了出现的原因,下面就看看如何解决这个问题
0.5px
其实第一反应就是能不能出现比 1px
小情况,譬如 CSS
中编写 0.5px
,那么在 devicePixelRatio
= 2 的屏幕上真实渲染物理像素就是 1px
,这样就可以达到还原设计稿的效果
但是 0.5px
这个属性值只有 Safari+和Firefox 支持
可以在 Mac
的 Safari
上测试下面的代码:
.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
的边框图
通常实践中因为边框图片尺寸都比较小的原因,都会在打包过程中使用 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
的地方却是使用元素来模拟的
对应的样式如下
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 相关的问题