每次写css像素带小数时,浏览器显示的实际值总是差了一丢丢,我们来找找原因。
我这里用的是102.0.5005.115版本的谷歌浏览器,如果发现文章和自己调试的数据有出入,可能是以下问题:
- 请打开电脑的设置把缩放和布局调成100%,因为你的电脑初始设置可能是125%之类的。
- 我用的是谷歌pc模式进行的测试,如果使用移动端请确保视口是100%,不建议使用移动端模拟器,因为dpr原因不方便观察。
先写一个demo:
<!DOCTYPE html>
<html lang="zn">
<head>
<title>亚像素测试</title>
<style>
* {
margin: 0;
padding: 0;
}
.box1 {
width: 10.6px;
height: 10.6px;
background: red;
}
.box2 {
width: 10.6px;
height: 10.6px;
background: blue;
}
div {
float: left;
}
</style>
</head>
<body>
<div class="box1"></div>
<div class="box2"></div>
</body>
</html>
我们打开控制台,发现.box1,.box渲染时候的尺寸是10.594px。好像和我们设置的没差多少,其实不然,其实浏览器是做了转换的,WebKit会使用subpixel layout(次像素/亚像素布局)的方式来渲染小数。谷歌浏览器默认会把1个物理像素分成64份(其他厂商的浏览器其实都有这样的操作,只是分成多少块可能存在差异,我们就只说我现在用的这个谷歌版本)。
也就是会计算亚像素,比如我们拿.box1为例子: 10.6取小数是0.6,我们来算一算0.6是在64分之几的一个区间:
38/64=0.59375,
39/64=0.609375
那么该取哪个呢,我们这里先直接说一下,取得是38/64,也就是最后算是10.59375 => 10.594
图1.1 10.6 => 10.594
那么这个小数的取舍规则就可能是向下取整或者是四舍五入,我们再来验证一下,将.box1的width变成10.609px在试试,如果是10.594那么就是向下取整,如果是10.609,那么就是四舍五入。
图1.2 10.609px => 10.594
所以我们可以知道,亚像素计算是向下取整的。
我们也可以得到一个公式:
亚像素 = floor(css中写得值 * 64) / 64
用.box width:10.6px为例子
亚像素=floor(10.6 * 64)/ 64 = 10.59375
因为计算出来的像素是小数,那么对于小数,浏览器是怎么渲染呢?我们知道实际的像素都是整数存在的。
<html lang="zn">
<head>
<title>亚像素测试</title>
<style>
* {
margin: 0;
padding: 0;
}
.box1 {
width: 10.6px;
height: 10.6px;
background: red;
}
.box2 {
width: 10.6px;
height: 10.6px;
background: blue;
}
div {
float: left;
}
</style>
</head>
<body>
<div class="box1"></div>
<div class="box2"></div>
</body>
</html>
我们再拿这个作为例子,看一下实际渲染出来是多少个物理像素。我用我手机的微距模式拍了一个照片,我们来数一下。
图1.3 .box1:11px,.box2:10px
发现.box1是11px,.box2是10px。en,,,,好像有点意思,具体是怎么回事呢?下面是我在网上找的一张图片:
图1.4
这是一种从subpixel到pixel转换的一种算法: 这里我简单说一下就好了,在最后面我会放上具体的链接:
enclosingIntRect 算法:把转换完的亚像素直接向上取整
pixelSnappedIntRect 算法: 直接 round 到离自己最近的一个物理像素
我们详细说一下pixelSnappedIntRect,我们先来具体计算一下,我们还是以这个为例子:
.box1{
width: 10.6px;
height: 10.6px;
background: red;
}
.box2{
width: 10.6px;
height: 10.6px;
background: blue;
}
subpixelWidth(.box1) = floor(10.6 * 64)/ 64 = 10.59375 => 10.594
subpixelWidth(.box2) = floor(10.6 * 64)/ 64 = 10.59375 => 10.594
pixelSnappedIntRect(.box1) = around(subpixelWidth(.box1)) = 11px(这里由于around一下,所以.box1多出来10.594-11=-0.406,这些亚像素会算在下一个元素上)
pixelSnappedIntRect(.box2) = around(subpixelWidth(.box1) + 前一个元素多出的像素) = around(10.594+(-0.406)) = 10px
所以第一个元素是11px,第二个元素是10px。
所以使用这种方法的话,渲染结果其实是有一定误差的,会有1px误差,这里需要注意一下: 这个1px误差指的是单个元素,并不是好几个元素整个布局下来整个的误差,而是单指一个元素的误差。这样的话其实在某些特定情况下是有问题的,比如说:
- 图片作为背景时图片显示不全,有一小部分被割裂了。
- 父盒子有边框的情况下,子盒子和父盒子之间存在缝隙
当然,出现小数的问题,大多应该出现在移动端,例如使用rem,vw做移动端适配,最后换算出来小数px的时候。这些问题我会再写一篇文章记录。
以下是参考的文章:
这篇应该是官方的文章: wikiLayoutUnit