为什么会存在 1px 问题?怎么解决?
在项目开发中一直深受
1px
的困扰,移动端展示的样式不是偏粗就是偏细、甚至无法看清。也许大家都尝试过或正在使用着各种解决方案,可是对于物理像素、逻辑像素、设备像素比等概念到底是什么,为什么会产生
1
像素等问题始终是一头雾水。。在进行了一番调研后,发现网上对于一些细节原理描述的都不太清晰。故本文结合了个人的一些理解,重点对其原理及实现进行探讨,希望能对像素相关问题彻底解惑。
为了便于更好的理解本文,下面对像素相关概念进行梳理。
像素
px
是图像显示的基本单元,相对单位。
设备像素(物理像素)
dp
device pixels,显示屏就是由一个个物理像素点组成,屏幕从工厂出来那天起物理像素点就固定不变了。也就是我们经常看到的手机分辨率所描述的数字。
设备独立像素(逻辑像素)
dip
device-independent pixels,就是我们手机的实际视口大小。是操作系统为了方便开发者而提供的一种抽象。程序与操作系统之间描述长度是以设备独立像素为单位。不随页面缩放、浏览器窗口大小而改变。
CSS像素
在 CSS 中使用的 px 都是指 CSS 像素。不考虑缩放情况下,1个 CSS 像素等于1个设备独立像素。
设备像素比
dpr
devicePixelRatio,是物理像素和设备独立像素的比值。
屏幕尺寸
inch
屏幕对角线长度
屏幕分辨率
Resoution
750*1334,手机屏幕纵、横方向像素点数,单位是px。常说的分辨率指的就是物理像素。相同大小的屏幕而言,屏幕分辨率越高显示的像素越多,单个像素尺寸较小,显示效果就越精细。
像素密度
dpi/ppi
概念
描述
dot per inch(pixels per inch),每英寸像素数,通过屏幕尺寸和分辨率来计算像素密度。也是屏幕出厂时就确定了。
简单来说就是像素单位基本分为三种:设备像素(物理像素)、设备独立像素(逻辑像素)、CSS 像素。下文将会围绕相关概念展开讨论。
话不多说,正文开始~~
为什么使用 1px 会出现问题
自从 2010 年 iPhone4 推出了 Retina 屏开始,移动设备屏幕的像素密度越来越高,于是便有了 2 倍屏、3 倍屏的概念。简单来说,就是手机屏幕尺寸没有发生变化,但屏幕的分辨率却提高了一倍,即同样大小的屏幕上,像素多了一倍。
那么我们获取到的 CSS 像素就不是真实的物理像素点了,于是便有了设备像素比的概念( devicePixelRatio 简称 dpr)。它用来描述屏幕物理像素与逻辑像素的比值。不同手机有不同的设备像素比,可参考 ⇲wiki 百科中对视网膜屏的描述 。
CSS 中的 1px 并不等于设备的 1px
对于前端来说,在高清屏出现之前,前端代码的 1px 即等于手机物理像素点的 1px。但有了 dpr 的概念之后,由于前端代码中的使用的是 CSS 像素,手机会根据 dpr 换算成实际的物理像素大小来渲染页面。比如 iPhone6 的设备像素比 dpr = 2 ,相当于一个 CSS 像素等于两个物理像素,即 1px 由 2个物理像素点组成。
那么问题来了,以 iPhone6 为例,其 dpr = 2、屏幕尺寸(CSS 像素) 为 375x667,一般设计稿提供 2 倍图尺寸为 750x1334 。那么设计稿中的 1px,对应屏幕尺寸其实应该写成 0.5px。再由 dpr 计算公式可知,0.5 * 2 = 1px 物理像素。
此时你应该已经发现了,设计稿要实现 1px 细线、1px 边框,为什么前端实现总是偏粗的?那是因为如果你在代码中直接写成 1px,再通过 dpr 计算之后其实是 2px 物理像素,并不符合设计稿的要求。
其实设计稿本质上要实现的是 CSS 像素的 !
那么当 dpr=2 时,代码中直接写成 0.5px 就解决问题了吗?
小数点像素 0.5px 的兼容性问题
其实在项目中,我们已经采用 rem 单位进行了设计稿与屏幕尺寸的换算,即把 1px 换算成了 0.5px 。但这种方案其实有各种各样的兼容性问题。
PC端
先上结论,在 PC 端浏览器的最小识别像素为 1px。
所以在开发阶段,当在开发者工具上进行页面调试时,可以看到即便代码中是 0.5px ,但默认会被浏览器识别并渲染为 1px。所以在浏览器看来总是偏粗了。如果你习惯用 PC 端的页面来进行视觉走查,那么结果可想而知...
上图中两个元素 width:200px;height:100px,分别为 border:0.5px、border:1px ,检查元素时可以看到页面上 border=0.5px 的元素计算大小后和 1px 效果是一样的,均是 width:202px;height:102px。说明浏览器都识别成 1px 了。
由上面的 gif 图也可以看到,因为设备 dpr=2,所以放大后 1px 的确是使用了2个物理像素点来渲染。并不是我们想实现的 0.5px。
移动端
在手机端,不同手机浏览器对小数点像素的处理效果就更千奇百怪了。
首先我们先来看一下采用 REM 布局方式下,代码中的 0.01rem 到底被换算成了多少?
这里简单说下REM 实现原理
rem(font size of the root element),即根据网页的根元素(html)来设置字体大小。和 em(font size of the element) 的区别是,em 是根据其父元素的字体大小来进行设置。
简单来说,rem 布局实现移动端适配的思想是,由于 rem 单位是根据页面根元素的 fontSize 来计算的,那么将 fontSize 设置成屏幕宽度 clientWidth 与设计稿宽度 750 的比值,那么我们按照设计稿的尺寸来重构页面的时候,使用 rem 单位即自动乘以 fontSize 计算出了适配不同屏幕的尺寸。
// 以750设计稿为例,计算rem font-size
let clientWidth = document.documentElement.clientWidth || document.body.clientWidth;
let ft = (clientWidth / 7.5).toFixed(2);
// 设置页面根字号大小
document.documentElement.style.fontSize = ft "px";
由上面的计算方式可知,不同屏幕宽度会计算出不同 fontSize ,那么 0.01rem 到底被换算成了多少呢?下面举例计算了几个机型的“1像素”大小
由表格可看出,不同手机计算的“1px”大小差别很大,而且手机本身对小数点的处理情况就存在较大的兼容性问题。
比如 IOS8 系列都已经支持 0.5px 了,可以借助媒体查询来处理,但是安卓手机对小数像素的表现形式却各不相同。网上关于不同型号手机浏览器对小数点的处理情况的资料较少,只知道在一些低版本的系统里,0.5px 将会被显示为 0px;有的能够画出半个像素的边,有的大于 0.55px 当成 1px,有的大于 0.75px 当成 1px,从表格计算结果来看是很难直接实现适配的。
比如 HUAWAI P30 的 0.01rem 计算后为 0.48px ,这种较小的小数像素其 border 已经无法正常展示了。
那么如何实现 1px 的效果?
在进行一番调研之后,发现目前的实现方案都离不开以下三种。
使用伪元素 CSS3``缩放的方式使用动态