Skip to content

探究 1px 问题

发布日期:2024-02-04

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就可以了吗? ​

0.3px边框的盒子
0.5px边框的盒子
1px 边框的盒子

部分安卓浏览器、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)
}

伪元素缩放 ​

使用了伪元素缩放的盒子,实现0.5px

通过为元素设置伪元素,设置 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实现手淘H5页面的终端适配

淘宝的flexible适配方案为什么只对iOS进行dpr判断,对于Android始终认为其dpr为1?

box-shadow ​

box-shadow: 0 -1px #D9D9D9, 1px 0 #D9D9D9, 0 1px #D9D9D9, -1px 0 #D9D9D9;border-radius: 16px;

缺点是占用 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 和 伪元素缩放两种方式是兼容性比较好的解决方案。

参考 ​

iOS 中 pt 与px的区分

前端移动端1px问题及解决方案