ベジェ曲線。優雅な響きですね。
そもそも、CSSでパス上をアニメーションさせたいという需要がないかと思いますが、
奇跡的に興味があるひとがいたら、参考にしていただければ。
なぜ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がスゴイ長さになるので、
可読性は皆無ですが、なんとか実現できました。
ジェネレータもあるので興味があるかたは使ってみてください。
ジェネレータ
マグロデモ
http://codepen.io/noplan1989/pen/gmMQjW
ソース
https://github.com/noplan1989/bezier-keyframe
補足 〜 曲線のアニメーションならanime.js
ここまでベジェ曲線上のアニメーションを紹介してきましたが、
anime.jsでSVGのパスをアニメーションできるみたいなので、
JSでやる場合はそっちのほうがいいと思います。
SVGの方が柔軟なうえに、なにより読みやすいです。
anime.js