1px 问题是前端开发中常见问题,会使得你的边框在某些设备上无法显示或者过粗过细,不想测试和设计找你麻烦的话,就要好好研究了o( ̄︶ ̄)o
1px 问题的成因
1px 问题是指在Retina显示屏(由苹果定义的,高像素密度的屏幕,可以使肉眼看不见单个物理像素点)上 1px 会变得很粗,显示出不止 1px 的大小。
这是因为在高像素密度的屏幕上,一个逻辑像素会以多个物理像素来渲染。比如对于 iphone6,其逻辑分辨率为 375x667pt,物理像素为 750x1334px,以两个物理像素渲染一个逻辑像素。
题外话:什么是pt?
对于 iphone 和 ios 开发来说,pt 是独立像素的意思,不随屏幕像素密度变化而变化,也就是逻辑像素。对于安卓来说是 dp(与密度无关的像素)。因此描述 iphone6 分辨率的时候,可以表示为 375x667pt。但通常我们搜索 pt 这个单位的含义的时候,通常会找到另一种答案:pt是印刷行业常用单位,等于1/72英寸。
于是,当设计师们在 750 宽度的设计稿(二倍设计稿)上,标示出 1px 的边框时,实际转换到逻辑像素是 0.5 px,因此如果前端还是写 1px,线条就会比设计稿来的粗。
直接写0.5px就可以了吗?
部分安卓浏览器、ios8 不支持 0.5px 写法,而且不同浏览器对小数点的四舍五入各不相同,0.5px 在不同设备可能会显示出不同粗细的线条,因此任何带有小数点的像素都可能出现类似的问题。而 pc 端也可能会把 0.5 px 渲染为 1px。
可以对 0.5px 兼容性进行判断,再实施不同兼容方案,测试 0.5px 兼容性的方法如下
if (dpr >= 2) {
var docEl = document.documentElement
var fakeBody = document.createElement('body')
var testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
docEl.appendChild(fakeBody)
if (testElement.offsetHeight === 1) {
docEl.classList.add('hairlines')
}
docEl.removeChild(fakeBody)
}
伪元素缩放
通过为元素设置伪元素,设置 1px 的边框,再缩放为原尺寸的二分之一,实现了0.5px 边框,css 代码如下
.border {
position: relative;
}
.border::after {
content: "";
box-sizing: border-box;
position: absolute;
left: 0;
top: 0;
width: 200%;
height: 200%;
border: 1px solid gray;
transform: scale(0.5);
transform-origin: 0 0;
border-radius: 16px;
}
基于 flexible 的动态设置 viewpoint
虽然 flexible 自适应方案目前已经停止维护,官方也表示该方案已过时,可以寻找其他现代化的适配方案。但这个方案在那时候(2014年5月)是非常出色的,因为2014年发布的 iphone6 为移动端适配带来了新的问题(多倍屏),flexble 方案的出现为前端开发者带来了全新的解决方案。
flexible 方案通过动态设置 viewpoint 的 meta 标签,将 initial-scale 值设置为 1/dpr,将页面缩放为原来的 1/dpr,从而使得 css 像素 = 物理像素。之后修改 html 元素的 font-size,使用 rem 布局将页面平分为10份,每份是1rem,假设缩放后页面的宽度是 750px,则 1rem = 75px,因此 font-size = 75px。当我们拿到 750px 设计稿时,我们需要画一个 100px 的元素,此时只需计算 100px/75 = 1.33rem 就可以得到 css 单位。于是无论设备屏幕像素怎样变化,1.33rem 换算到屏幕上总能符合设计稿的尺寸,还原设计稿的时候就只需要计算 css像素rem = 元素像素/(设计稿尺寸*10)
,这个过程也可以由编辑器插件或者 postcss 的 px2rem 完成。
回到 1px 问题上,由于进行了页面缩放使得 css 像素 = 物理像素,因此我们照常将 1px 进行上述换算,rem 转换回 px 之后也会得到 1px(但不排除在转化过程中会产生小数点),避免了使用 0.5px 导致的兼容性问题。但由于安卓设备兼容性不佳,这个解决方案在安卓设备上固定不进行缩放,因此使用此方案在安卓上无法完美解决 1px 问题。
淘宝的flexible适配方案为什么只对iOS进行dpr判断,对于Android始终认为其dpr为1?
box-shadow
缺点是占用 box-shadow 属性。
background
缺点是无法实现圆角,在 pc 上显示异常。
.background-border {
background-image: linear-gradient(180deg, red, red 50%, transparent 50%), linear-gradient(
270deg,
red,
red 50%,
transparent 50%
), linear-gradient(0deg, red, red 50%, transparent 50%), linear-gradient(90deg, red, red 50%, transparent
50%);
background-size: 100% 1px, 1px 100%, 100% 1px, 1px 100%;
background-repeat: no-repeat;
background-position: top, right top, bottom, left top;
padding: 10px;
}
查看设备 dpr
查询设备dpr通常使用window.devicePixelRatio
,这个属性在 caniuse 上的兼容性达到 98%,基本上不用担心无法支持的情况。
根据window.devicePixelRatio
,当前设备的 dpr 是:
对于css可以使用 @media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5)
来进行媒体查询。
总结
background 渐变、box-shadow 模拟等,都存在缺点,比如限制了正常的 box-shadow 和 background 的使用;直接使用 0.5 px 会存在兼容性问题;viewpoint 和 伪元素缩放两种方式是兼容性比较好的解决方案。