这是一个写CSS Transform不看文档的悲惨故事。

事情要从实现图片放大预览动画开始说起。动画的设计是,从缩略图的位置开始,最后铺满屏幕。原本的方案是调整位置属性和长宽来实现。然而这个方案完成后,得到的动画非常的卡。打开Performance一看Composite Layers果不其然地占满了好几帧的主线程时间。

查了下怎么优化动画,答案是使用transform+transition把元素放到合成层。这样浏览器不需要去关心位置变化是否导致了其他元素位置的变化。transform的工作原理也很简单,里边有几个变换函数,然后浏览器会把这些变换函数的结果最终合成为一个变换矩阵,然后直接乘到渲染结果上就行了。对浏览器而言很轻松,对开发者而言也很轻松。

把刚才的动画变成transform实现的话,比较合理的方法是把元素固定在屏幕左上角,然后缩放与平移。两个值都很容易计算。

元素用绝对定位固定好了,问题来了,元素老是定位不到想要的位置。仔细一看,这个缩放怎么和图形学里面讲的缩放不一样,原本左上角在(0,0)的元素缩放以后左上角的点往左上角又偏了?

scale()不应该改变元素位置啊

此时之前学的一丁点的图形学的知识开始占据了大脑。CSS动画优化相关的教程肯定会告诉你,CSS transform使用显卡加速,也就是说,它使用图形学的那一套直接操作对应元素的渲染结果,浏览器不需要进行重排和重绘的操作,因此减少了对动画过程对性能的负担。而图形学的“那套操作”,自然就是线性变换了。我们在transform属性里输入的translate()、rotate()这类函数都会被浏览器转换为对应的矩阵,然后用渲染结果乘这个变换矩阵。为什么我知道的这么清楚呢?因为我还专门去找了规范......

都找到规范了,其实我只要稍微往上就能看到那个属性了。然而我只看了transform属性是怎么变成矩阵的那一章节,里面也丝毫没提变换中心的问题啊!

那怎么办?查规范也没有结果。那我只好观察一下这个偏移量。调用元素的getBoundingClientRect()方法可以获得transform属性计算后元素在屏幕上的左上角点的坐标。观察下来,发现这个偏移量很有规律,似乎和元素的长宽有着什么关系。这时候统计学的直觉告诉我,可以做个回归。

于是,我修改元素的尺寸,然后把收集到的几组数据丢进Excel跑了个回归,得到了几乎贴近1的R方。也就是说,如果排除浮点数的精度问题,这几个变量之间有明确的线性关系。最后,得到这样一个表达式:

[code lang="javascript"]`translate(${(targetWidth-width)/2+(window.innerWidth-targetWidth)/2}px,${(targetHeight-height)/2+(window.innerHeight-targetHeight)/2}px) scale(${scaleX},${scaleY})`[/code]

加上了这样一个偏移后,元素缩放时就能保持在左上角了。这个过程中还发现由于transform属性会合并变换矩阵,所以变换函数的位置是会影响变换结果的。不过,弄完以后,总觉得有点怪怪的。越想越不对啊,于是拿scale() center web这串关键词搜了下,想不到正好有篇StackOverflow...

底下老哥的回复,非常的简短,但是字里行间写满了对新手的友情关怀:

You need to use

[code lang="css"]transform-origin: center;[/code]

Take a look at some of the docs on this https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin

Stackoverflow

草。所以我忙活这么久的东西,其实调整transform-origin就能解决吗?然而,transform的MDN文档对变换中心的问题只字未提,只能在相关链接看到有这么个东西。害,看文档还是得看全。

不过虽然知道了更简单的方法,这个基于回归得到的表达式还是没有删了,就这样一直放在代码里边。这可花了我一个下午弄呢。

啊,那段代码就在现在站里用的这个点击图片放大的里头。

标题图片转自知乎专栏,不过好像应该是来源于一本国外的教程