Help us understand the problem. What is going on with this article?

ベジェ曲線上にマグロをトレースさせるCSSアニメーション

More than 1 year has passed since last update.

ベジェ曲線。優雅な響きですね。

そもそも、CSSでパス上をアニメーションさせたいという需要がないかと思いますが、
奇跡的に興味があるひとがいたら、参考にしていただければ。

CSS Bezier Curve Path Animation_small.png

なぜCSSなのか

ふと曲線的なアニメーションを作りたいと思って、
ベジェ曲線なるものがあることを知ってJSで実装したのですが、
Styleを毎フレームごりごりと書き換えているせいか、
スマホで若干カクついてしまう時があり、CSSだけでやろう。と思うに至りました。

「それ、WebGLでやったら軽いよ」と、どこからか聞こえてきそうですが、
CSSでやることに意味がある、と自分に言い聞かせます。

ベジェ曲線をJSで描く

まずベジェ曲線のなんたるかを知らなかったので、早々に心が折れそうになります。

詳しい内容は、わかりやすい解説記事があるので、
そちらを見ていただいたほうが早いかと思いますが、
雑にいうとソフトウェアで曲線を描く手段のひとつになります。

一から学ぶベジェ曲線
中学生でもわかるベジェ曲線

Wikipediaに数式があったので、
それをもとに3次ベジェ曲線をJSで書くとこんな感じに。

const bezier = (t) => {
  return {
    x: Math.pow(1 - t, 3) * start.x + 3 * Math.pow(1 - t, 2) * t * p1.x + 3 * (1 - t) * Math.pow(t, 2) * p2.x + Math.pow(t, 3) * end.x,
    y: Math.pow(1 - t, 3) * start.y + 3 * Math.pow(1 - t, 2) * t * p1.y + 3 * (1 - t) * Math.pow(t, 2) * p2.y + Math.pow(t, 3) * end.y
  };
}

始点(start)、終点(end)、制御点(p1,p2)のxy座標を与えてあげて、
tを0=>1へ徐々に変化させると曲線になります。

具体的にJSでベジェ曲線上を動かす場合であればこんな感じです。
デモ

const circle = document.getElementById('circle')
const start  = {x: 0, y: 0}
const end    = {x: 100, y: 100}
const p1     = {x: 100, y: 300}
const p2     = {x: 200, y: 70}
const startTime = Date.now()
const duration  = 3000

const transform = (elm, x, y) => {
  elm.style.transform = `translate3d(${x}px, ${y}px, 0px)`
}

const bezier = (t) => {
  return {
    x: Math.pow(1 - t, 3) * start.x + 3 * Math.pow(1 - t, 2) * t * p1.x + 3 * (1 - t) * Math.pow(t, 2) * p2.x + Math.pow(t, 3) * end.x,
    y: Math.pow(1 - t, 3) * start.y + 3 * Math.pow(1 - t, 2) * t * p1.y + 3 * (1 - t) * Math.pow(t, 2) * p2.y + Math.pow(t, 3) * end.y
  }
}

// tの値が3000msかけて0=>1に変化します
!(function render() {
  const elapsed = Date.now() - startTime
  const t = elapsed / duration

  if (t < 1) {
    const current = bezier(t)
    transform(circle, current.x, current.y)
    window.requestAnimationFrame(render)
  } else {
    transform(circle, end.x, end.y)
  }
})()

CSSで描く

これをCSSのみで実現するには、animationで実現できる形にする必要があります。
とりあえず、JSでごりごり計算したものを、keyframesで吐き出すことに。
デモ

// SASS出力の例(一部省略)
const styles = []
const frame  = Math.floor(duration / 16)

styles.push('@keyframes anime-bezier')

for (let i = 0; i < frame; i++) {
  if (i === frame - 1) {
    styles.push('  100%')
    styles.push(`    transform: translate3d(${end.x}px, ${end.y}px, 0px)`)
  } else {
    const t = i / frame
    const current = bezier(t)
    styles.push(`  ${t * 100}%`)
    styles.push(`    transform: translate3d(${current.x}px, ${current.y}px, 0px)`)
  }
}

const text = styles.join('\r\n')
const textNode = document.createTextNode(text)
document.getElementById('hoge').appendChild(textNode)

結果、こんな感じになります。

.anime
  animation: 2000ms linear anime-bezier

@keyframes anime-bezier
  0%
    transform: translate3d(0px, 0px, 0px)
  4%
    transform: translate3d(1px, 2px, 0px)
  8%
    transform: translate3d(2px, 3px, 0px)
  // 略

アニメーション時間が長くなると、keyframesがスゴイ長さになるので、
可読性は皆無ですが、なんとか実現できました。

ジェネレータもあるので興味があるかたは使ってみてください。

ジェネレータ

bezier keyframe generator_small.png

https://bezier.noplan.cc

マグロデモ

CSS Bezier Curve Path Animation_small.png

http://codepen.io/noplan1989/pen/gmMQjW

ソース

https://github.com/noplan1989/bezier-keyframe

補足 〜 曲線のアニメーションならanime.js

ここまでベジェ曲線上のアニメーションを紹介してきましたが、
anime.jsでSVGのパスをアニメーションできるみたいなので、
JSでやる場合はそっちのほうがいいと思います。

SVGの方が柔軟なうえに、なにより読みやすいです。
anime.js

nishinoshake
AWSの料金をざっくり計算できるサイトや、邦画の予告を朝まで観られるサイトを作ってます。
https://noplan.cc
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away