CSS animation 和 transition 均能很好地实现 A 到 B 的过渡,但这仅限于直线运动。无论你如何调整元素的 animation 或 transition 的 贝塞尔曲线(*-timing-function),均不能使其沿曲线运动。当然也包括自定义过渡函数,如弹簧效果。这是因为 X 轴与 Y 轴的相对位移总是相等的。
无疑 JavaScript 能轻易实现曲线运动,但这里有一个简单方法能突破这个限制:分层动画。通过使用两个或以上的元素去驱动一个动画,即对元素的路径进行细粒度的控制,分别为 X 轴和 Y 轴应用不同的过渡函数(*-timing-function)。
原文:Moving along a curved path in CSS with layered animation
CSS animation 和 transition 均能很好地实现 A 到 B 的过渡,但这仅限于直线运动。无论你如何调整元素的 animation 或 transition 的 贝塞尔曲线(
*-timing-function
),均不能使其沿曲线运动。当然也包括自定义过渡函数,如弹簧效果。这是因为 X 轴与 Y 轴的相对位移总是相等的。无疑 JavaScript 能轻易实现曲线运动,但这里有一个简单方法能突破这个限制:分层动画。通过使用两个或以上的元素去驱动一个动画,即对元素的路径进行细粒度的控制,分别为 X 轴和 Y 轴应用不同的过渡函数(
*-timing-function
)。问题所在
See the Pen css-curve-1 by Jc (@JChehe) on CodePen.
在深入研究解决方案前,先仔细研究一个问题。CSS animation 和 transition 限制着元素仅能沿直线运动,即总是执行 A 到 B 的最短路径,这很适合大多数情况。但却缺乏一种方式告诉 CSS 应使用“更佳路径”而不是“最短路径”。
在 CSS 中,两点位移过渡的最直接方式是使用 transform 的 translate 属性,但这仅能产生直线运动。在以下
@keyframes
中,元素会在 (0, 0) 和 (100, -100) 间来回移动:这并不复杂。为了得到问题的解决方案,我们需要将动画进行拆分(至少在视觉上)。
我们从 0% 的 (0, 0) 开始,并在 50% 使用 translate3d(100px, -100px, 0) 将元素位移至 (100, -100),最后原路返回。换一种思考方式,元素分别向右位移 100px 和向上位移 100px,两者一结合就会产生一定角度的直线位移。
See the Pen css-curve-2 by Jc (@JChehe) on CodePen.
解决方案:为每轴分别指定一个过渡函数
那么我们如何创建前面案例提及的曲线运动呢?要创建非直线运动,需要为 X 轴与 Y 轴指定不同的运动速度。
前面案例均使用
linear
过渡函数。但这里我们要为元素添加一个父元素,并为父元素 X 轴与 Y 轴分别应用不同的过渡函数。下面将为 X 轴应用ease-in
,为 Y 轴应用ease-out
:See the Pen css-curve-3 by Jc (@JChehe) on CodePen.
实现:一轴对应一元素
不幸的是,CSS 不支持为 transform 叠加多个 animation,否则仅最后声明的 animation 有效。那么我们该如何组合两个 animation 呢?首先,先将一个元素放在另一个元素中,然后对容器元素指定一个 animation,再对子元素指定另一个不同的 animation 即可。
在上述所有曲线运动中,我们均能看到两个分离的元素在运动,而(曲线运动的)容器元素(即父元素)则完全透明。为了能清楚看到两个元素如何结合得到曲线运动,我们为容器元素添加边框:
See the Pen css-curve-4 by Jc (@JChehe) on CodePen.
dot 元素是边框元素的子元素。边框元素沿 X 轴水平运动,而 dot 自身沿 Y 轴上下竖直运动。移除父元素的边框后,就可得到我们预期中的曲线运动。利用伪元素则无需在 HTML 中创建两个元素。
假设有以下 HTML 代码:
那么可以像下面那样添加伪元素:
然后添加两个独立的 animation 代码块:一个用于 X 轴,一个用于 Y 轴。其中第一个使用
ease-in
,第二个使用ease-out
:带上 WebKit 内核前缀,并使用自定义贝塞尔曲线替换
ease-in
和ease-out
,就能得到文章最开始的效果:这就是文章最开始的效果:
See the Pen css-curve-5 by Jc (@JChehe) on CodePen.
你可能注意到:目前所有案例均使用了
@keyframes
代码块,但这纯粹是因为需要几个关键帧来实现来回移动的动画。换句话说,分层动画也适用于transition
属性,特别是 A 到 B 的这类动画。对于绝对定位的元素,你可以通过设置单个元素的
left
和bottom
属性实现曲线运动,从而避免使用容器元素。但这并不推荐,因为动画的每一帧均会触发重绘,导致性能低下。使用具有伪元素的分层动画,能利用硬件加速的translate
属性产生漂亮丝滑的动画。译者基于本文实现了以下效果:
See the Pen css-curve-final by Jc (@JChehe) on CodePen.