Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【译】使用 CSS 分层动画实现曲线运动 #27

Open
JChehe opened this issue Aug 13, 2018 · 0 comments
Open

【译】使用 CSS 分层动画实现曲线运动 #27

JChehe opened this issue Aug 13, 2018 · 0 comments

Comments

@JChehe
Copy link
Owner

JChehe commented Aug 13, 2018

原文: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.

<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

在深入研究解决方案前,先仔细研究一个问题。CSS animation 和 transition 限制着元素仅能沿直线运动,即总是执行 A 到 B 的最短路径,这很适合大多数情况。但却缺乏一种方式告诉 CSS 应使用“更佳路径”而不是“最短路径”。

在 CSS 中,两点位移过渡的最直接方式是使用 transform 的 translate 属性,但这仅能产生直线运动。在以下 @keyframes 中,元素会在 (0, 0) 和 (100, -100) 间来回移动:

@keyframes straightLine {
  50% {
    transform: translate3D(100px, -100px, 0);
  }
}

.dot {
  animation: straightLine 2.5s infinite linear;
}

这并不复杂。为了得到问题的解决方案,我们需要将动画进行拆分(至少在视觉上)。

我们从 0% 的 (0, 0) 开始,并在 50% 使用 translate3d(100px, -100px, 0) 将元素位移至 (100, -100),最后原路返回。换一种思考方式,元素分别向右位移 100px 和向上位移 100px,两者一结合就会产生一定角度的直线位移。

See the Pen css-curve-2 by Jc (@JChehe) on CodePen.

<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

解决方案:为每轴分别指定一个过渡函数

那么我们如何创建前面案例提及的曲线运动呢?要创建非直线运动,需要为 X 轴与 Y 轴指定不同的运动速度

前面案例均使用 linear 过渡函数。但这里我们要为元素添加一个父元素,并为父元素 X 轴与 Y 轴分别应用不同的过渡函数。下面将为 X 轴应用 ease-in,为 Y 轴应用 ease-out

See the Pen css-curve-3 by Jc (@JChehe) on CodePen.

<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

实现:一轴对应一元素

不幸的是,CSS 不支持为 transform 叠加多个 animation,否则仅最后声明的 animation 有效。那么我们该如何组合两个 animation 呢?首先,先将一个元素放在另一个元素中,然后对容器元素指定一个 animation,再对子元素指定另一个不同的 animation 即可。

在上述所有曲线运动中,我们均能看到两个分离的元素在运动,而(曲线运动的)容器元素(即父元素)则完全透明。为了能清楚看到两个元素如何结合得到曲线运动,我们为容器元素添加边框:

See the Pen css-curve-4 by Jc (@JChehe) on CodePen.

<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

dot 元素是边框元素的子元素。边框元素沿 X 轴水平运动,而 dot 自身沿 Y 轴上下竖直运动。移除父元素的边框后,就可得到我们预期中的曲线运动。利用伪元素则无需在 HTML 中创建两个元素。

译者注:部分移动设备中,animation / transition 对伪元素无效。

假设有以下 HTML 代码:

<div class="dot"></div>

那么可以像下面那样添加伪元素:

.dot {
  /* Container. Animate along the X-axis */
}

.dot::after {
  /* Render dot, and animate along Y-axis */
}

然后添加两个独立的 animation 代码块:一个用于 X 轴,一个用于 Y 轴。其中第一个使用 ease-in,第二个使用 ease-out

.dot {
  /* Some layout code… */
  animation: xAxis 2.5s infinite ease-in;
}

.dot::after {
  /* Render dot */
  animation: yAxis 2.5s infinite ease-out;
}

@keyframes xAxis {
  50% {
    animation-timing-function: ease-in;
    transform: translateX(100px);
  }
}

@keyframes yAxis {
  50% {
    animation-timing-function: ease-out;
    transform: translateY(-100px);
  }
}

带上 WebKit 内核前缀,并使用自定义贝塞尔曲线替换 ease-inease-out,就能得到文章最开始的效果:

.demo-dot {
  -webkit-animation: xAxis 2.5s infinite cubic-bezier(0.02, 0.01, 0.21, 1);
  animation: xAxis 2.5s infinite cubic-bezier(0.02, 0.01, 0.21, 1);
}

.demo-dot::after {
  content: '';
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 20px;
  background-color: #fff;
  -webkit-animation: yAxis 2.5s infinite cubic-bezier(0.3, 0.27, 0.07, 1.64);
  animation: yAxis 2.5s infinite cubic-bezier(0.3, 0.27, 0.07, 1.64);
}

@-webkit-keyframes yAxis {
  50% {
    -webkit-animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
    animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
    -webkit-transform: translateY(-100px);
    transform: translateY(-100px);
  }
}

@keyframes yAxis {
  50% {
    -webkit-animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
    animation-timing-function: cubic-bezier(0.02, 0.01, 0.21, 1);
    -webkit-transform: translateY(-100px);
    transform: translateY(-100px);
  }
}

@-webkit-keyframes xAxis {
  50% {
    -webkit-animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
    animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
    -webkit-transform: translateX(100px);
    transform: translateX(100px);
  }
}

@keyframes xAxis {
  50% {
    -webkit-animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
    animation-timing-function: cubic-bezier(0.3, 0.27, 0.07, 1.64);
    -webkit-transform: translateX(100px);
    transform: translateX(100px);
  }
}

这就是文章最开始的效果:

See the Pen css-curve-5 by Jc (@JChehe) on CodePen.

<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

你可能注意到:目前所有案例均使用了 @keyframes 代码块,但这纯粹是因为需要几个关键帧来实现来回移动的动画。换句话说,分层动画也适用于 transition 属性,特别是 A 到 B 的这类动画。

对于绝对定位的元素,你可以通过设置单个元素的 leftbottom 属性实现曲线运动,从而避免使用容器元素。但这并不推荐,因为动画的每一帧均会触发重绘,导致性能低下。使用具有伪元素的分层动画,能利用硬件加速的 translate 属性产生漂亮丝滑的动画。


译者基于本文实现了以下效果:

See the Pen css-curve-final by Jc (@JChehe) on CodePen.

<script async src="https://static.codepen.io/assets/embed/ei.js"></script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant