说好一起变圆,你怎么就瘦了?
原文链接 http://judes.me/frontend/2018/01/07/why_circle_not_round.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
序
前几天,同事让我看看一个问题。
他让几个圆并排成一直线,很简单的一件事情。
排成一行的 7 个圆
最后视觉验收不通过,因为这里面有几个圆变扁了。用工具量一下,确实如此:
有的圆变扁了
我仔细看这一排 7 个圆,发现不是所有的圆都变扁了,变扁的只有两三个。从前端代码上看所有的圆都应用相同的样式:
圆的样式相同
从浏览器计算出来的数值上看所有的圆直径相同:
圆的直径相同
当我把圆的直径设为整数时,所有的圆都正常显示。
直径为整数的圆
经过在网上一番调查与思考,我终于明白了原因。
为什么浏览器上显示的数值与渲染效果不一样?
原来根据规范,浏览器在计算属性的值时,最终会得出一个叫做 used value
的“绝对理论值”。但浏览器在渲染时可能不能直接使用这个 used value
,这时只能使用这个值的近似值,这个近似值叫做 actual value
。
Chrome 浏览器的调试工具中显示的所有数值,是 used value
,调用 DOMelement.getBoundingClientRect()
也能得到 DOMelement
的所有 used value
。而我们肉眼看到的尺寸是用 actual value
渲染出来的。
用 getBoundingClientRect 得到 used value
为什么小数会让圆变扁
因为浏览器只能渲染整数倍象素长度,我们的圆的直径是小数,所以只能用近似后的整数值。
但为什么有些圆看上去是正常的呢?
有些圆看上去正常,有些却变扁,要弄清楚这个现象,得先了解 actual value
的计算公式。
采用 webkit 内核的浏览器,actual value
的计算具体公式是这样的:
定义几个变量:
x : 圆在横轴上方向位置, used value
y : 圆在纵轴上方向位置, used value
width : 圆的宽, used value
height : 圆的长, used value
x' : 圆在横轴上方向的实际位置, actual value
y' : 圆在纵轴上方向的实际位置, actual value
width' : 圆的实际宽, actual value
height' : 圆的实际长, actual value
actual value 计算公式:
x' = round(x)
y' = round(y)
width' = round(x + width) - round(x)
height' = round(y + height) - round(y)
这些圆横着排成一排,所以它们的 y 和 height 都是一样的,计算后的 y' 和 height' 也一样,而因为 x 值都不一样,计算后的 width' 就有可能不一样,不过所有圆之间的 width' 最多相差 1 。
最终看到的效果就是,有几个圆是正常的(height' 跟 width' 值一样),而另外几个圆在相同的方向上(横轴或纵轴)变扁。圆的直径越小,变扁就越明显。
下图用公式计算第一个圆与第二个圆的直径,可以看到两者在横轴上相差 1 :
用公式计算 actual value
有办法让圆的实际尺寸跟设计稿一致吗?
要让圆的实际尺寸跟设计稿一致,除了设置圆的直径(used value)为整数,没有别的办法。
注意,如果圆的直径以 em/rem/vw 等等为单位,经过计算后的以 px 作为单位的值就已经跟设计稿的值有差距。
因为各个浏览器在处理小数时保留多少位都有自己的实现,再经过乘法计算后,最终的值很难跟设计稿上的对得上。
如果圆的直径(used value)一定要是小数,只能做到尽量跟设计稿差距小一点。
如果适配 webkit 内核浏览器的话,建议把这个小数定为 1/64 的倍数。这个 1/64
是 webkit 内核浏览器的精度,浏览器会把值放大 64 倍、取整后保存下来,使用时取出来直接除以 64 ,保留小数。
为什么是 64 ?可能是左移 8 位(位运算)速度比较快吧。