SVGのアニメーションを実装する機会がありmo.jsを使用したので、備忘録も兼ねて基本的な使い方を紹介したいと思います。
公式チュートリアルが充実しているのでこちらを読めば大丈夫だと思いますが、日本語の記事がほとんどないので英語アレルギーの方は参考にしてみてください。
執筆時点でのmo.jsのバージョンは0.288.2です。
セットアップ
npm
mo.jsをインストール。
npm i @mojs/core
インストールしたらmo.jsを使用するjsファイルで読み込む。
import mojs from '@mojs/core';
CDN
以下をHTMLに記述する。
<script src="https://cdn.jsdelivr.net/npm/@mojs/core"></script>
SVGを生成する
自分でSVGを用意しなくても、簡単な形であればmo.jsを使って生成することができます。
(自分で用意したSVGを使う方法は後述。)
円を描画する
new mojs.Shape({
parent: '#parent', // SVGの親要素を指定する
shape: 'circle', // 描画するSVGの指定
radius: 10, // 円のサイズ
fill: 'deeppink', // 塗りの色
isShowStart: true // 最初から描画しておくかどうか
});
shape
に'circle'
を指定するとellipse
タグを使ったSVGを生成してくれます。
circle
タグではなくellipse
タグで生成されるため、楕円にすることもできます。
楕円にしたい場合はradius
ではなくradiusX
、radiusY
で縦幅、横幅をそれぞれ指定しましょう。
また、デフォルトではisShowStart
がfalse
になっています。
これをtrue
にしておかないとアニメーションが実行されるまで非表示になってしまうため、最初から表示しておきたいものはtrue
指定するのを忘れないようにしましょう。
四角形を描画する
new mojs.Shape({
parent: '#parent',
shape: 'rect',
radius: 10,
fill: 'cyan',
isShowStart: true
});
shape
に'rect'
を指定するとrect
タグを使ったSVGを生成してくれます。
オプションの指定はcircle
と同じ感じで大丈夫です。
多角形を描画する
new mojs.Shape({
parent: '#parent',
shape: 'polygon',
points: 6, // 角の数
radius: 10,
fill: 'yellow',
isShowStart: true
});
shapeに'polygon'
を指定するとpath
タグでSVGを生成してくれます。
オプションの指定は基本的に'circle'
、'rect'
と同じですが、points
で角の数を指定することができます。
デフォルト値は3なので指定していなければ三角形が描画されます。
ジグザグ線を描画する
new mojs.Shape({
parent: '#parent',
shape: 'zigzag',
points: 11,
radius: 25,
fill: 'none',
stroke: 'deeppink', // アウトラインの色
isShowStart: true
});
shapeに'zigzag'
を指定するとpath
タグでSVGを生成してくれます。
オプションの指定は'polygon'
と同じ感じで大丈夫です。
fill
にnone
を指定して塗りを無効にしておかないと、三角形が横並びになっているような感じで描画されます。
合わせてstroke
に線の色を指定しておきましょう。
曲線を描画する
new mojs.Shape({
parent: '#parent',
shape: 'curve',
radius: 25,
fill: 'none',
stroke: 'cyan',
isShowStart: true
});
shapeに'curve'
を指定するとpath
タグでSVGを生成してくれます。
オプションの指定は'circle'
、'rect'
と同じ感じで大丈夫です。
fill
にnone
を指定して塗りを無効にしておかないと、線の内側が塗りつぶされた状態で描画されます。
交差した線を描画する
new mojs.Shape({
parent: '#parent',
shape: 'cross',
radius: 25,
stroke: 'yellow',
isShowStart: true
});
shapeに'cross'
を指定するとpath
タグでSVGを生成してくれます。
オプションの指定は基本的に'circle'
、'rect'
と同じ感じですが、fill
を指定しても何も変わりません。
その他の指定可能なオプションやコールバックはこちらに一覧が載っています。
実際の使用例
See the Pen mo.js - Draw Shapes Sample by yumetomo (@srbangs) on CodePen.
アニメーションを実装する前に
Bounce
系のアニメーションを付けると、素の状態だとはみ出した分が見切れてしまいます。
CSSで以下の指定をしておきましょう。
svg {
overflow: visible;
}
無限ループのアニメーション
円のサイズが20→80→20→80→20→...と無限リピートするアニメーションを作ってみます。
new mojs.Shape({
parent: '#parent',
shape: 'circle',
fill: '#F64040',
radius: { 20 : 80 }, // { start : end }
duration: 2000, // アニメーションの秒数
isYoyo: true, // リピートする時に再生→逆再生→再生にするかどうか
isShowStart: true,
easing: 'elastic.inout', // アニメーションのイージング
repeat: 1, // リピートする回数
// リピート分を含めてアニメーションが全て終わったら呼び出されるcallback
onComplete() {
// この時thisの参照先はShapeオブジェクトになる
this.replay();
}
}).play();
アニメーションで変化させたいオプションはradius: { 20 : 80 }
のようにオブジェクトで渡す。
isYoyo
オプションは初期値false
なので、指定していなければ再生→初期値に戻る→再生の繰り返し、true
を指定すると再生→逆再生→再生の繰り返しになる。
また、逆再生はリピート扱いなのでrepeat
オプションに最低でも1を指定していないと逆再生されない点に注意しましょう。
リピートする回数が決まっている場合はその回数をrepeat
オプションにそのまま渡せば大丈夫ですが、repeat
オプションで無限ループの指定をすることはできないので、無限ループしたい場合はonComplete()
のコールバック関数内でreplay()
メソッドを呼び出しもう一度最初からアニメーションをやり直します。
easing
オプションに指定できる値はこちらを参照してみてください。
複数のアニメーションを繋げる
四角形が回転→内側から塗りが消えていくという流れを作ってみます。
new mojs.Shape({
parent: '#parent',
shape: 'rect',
fill: 'none',
stroke: '#FC46AD',
radius: 10,
strokeWidth: 20,
angle: { '-180': 0 },
duration: 900,
easing: 'bounce.out',
})
.then({
strokeWidth: 0,
scale: 2,
duration: 800,
easing: 'sin.in',
onComplete() {
this.replay();
}
})
.play();
then
で繋げることで一つのSVGに対して順番にアニメーションを実行することができます。
最初に指定したオプションは後から上書きしない限り維持されます(上の例だとfill
とstroke
は後から変わっていないのでずっと初期値のまま)。
複数のSVGを順番にアニメーションさせる
こういう時はTimeline
の出番です。
const CIRCLE = new mojs.Shape({
parent: '#parent',
shape: 'circle',
radius: 30,
scale: { 0: 1 },
fill: 'none',
stroke: 'deeppink',
strokeWidth: { 30: 0 },
x: 'rand(-50, 50)', // 引数を元にランダムな値が入る - (min, max)
y: 'rand(-50, 50)',
duration: 300,
// このSVGのアニメーションが完了した時点で呼び出される
onComplete() {
// rand()の値を再生成する
this.generate();
}
});
const RECT = new mojs.Shape({
parent: '#parent',
shape: 'rect',
radius: 30,
scale: { 0: 1 },
fill: 'none',
stroke: 'cyan',
strokeWidth: { 30: 0 },
x: 'rand(-50, 50)',
y: 'rand(-50, 50)',
delay: 150,
duration: 300,
onComplete() {
this.generate();
}
});
const POLYGON = new mojs.Shape({
parent: '#parent',
shape: 'polygon',
radius: 30,
points: 5,
scale: { 0: 1 },
fill: 'none',
stroke: 'yellow',
strokeWidth: { 30: 0 },
x: 'rand(-50, 50)',
y: 'rand(-50, 50)',
delay: 300,
duration: 300,
onComplete() {
this.generate();
}
});
const TIMELINE = new mojs.Timeline({
// Timelineに登録されている全てのアニメーションが完了したら呼び出される
onComplete() {
this.replay();
}
});
TIMELINE.add(CIRCLE, RECT, POLYGON);
TIMELINE.play();
円と四角形と五角形のSVGを生成し、Timeline
オブジェクトにadd()
メソッドで追加して、play()
メソッドを実行すると登録されているSVGのアニメーションが実行されます。
Staggerと組み合わせる
Timeline
に登録するSVGがそれぞれ全く違う性質であれば良いですが、↑のようにほとんど同じようなSVGが複数ある場合1つ1つ生成していくのは非常に冗長です。
共通の部分だけ変数に持たせておくという手もありますが、Stagger
を使うとより簡潔にまとめられます。
const STAGGER = mojs.stagger(mojs.Shape);
const SHAPES = new STAGGER({
parent: '#parent',
quantifier: 3, // 生成するSVGの数
shape: ['circle', 'rect', 'polygon'], // 左から順番に参照される
radius: 30,
scale: { 0: 1 },
fill: 'none',
stroke: ['deeppink', 'cyan', 'yellow'], // 左から順番に参照される
strokeWidth: { 30: 0 },
x: 'rand(-50, 50)',
y: 'rand(-50, 50)',
duration: 300,
delay: 'stagger(150)', // 0から始まり、150刻みで増えていく
onComplete() {
this.generate();
}
});
const TIMELINE = new mojs.Timeline({
onComplete() {
this.replay();
}
});
TIMELINE.add(SHAPES);
TIMELINE.play();
quantifier
コメントで書いてある通り、SVGを何個生成するか指定します。
整数を渡せばその数だけ生成されますが、例えば'shape'
という文字列を渡すとshape
オプションに指定されている配列の中身の数が渡されます。
quantifier: 'shape', // shapeオプションの配列の中身の数(3)が渡される
shape: ['circle', 'rect', 'polygon']
quantifierの値 > 配列の中身の数な場合
例えば以下のような場合。
quantifier: 5,
shape: ['circle', 'rect', 'polygon']
この場合はcircle
→rect
→polygon
→circle
→rect
という順番で値が渡される。
つまりquantifier
の値の方が大きい場合、溢れた分はもう1度配列の先頭から参照するようになっている。
stagger()
渡された値刻みで増えていく。
第1引数が初期値、第2引数が増加量。
引数を1つだけにした場合は第2引数の増加量として扱われ、初期値は0になる。
quantifier: 3,
duration: 'stagger(150)', // 0→150→300
delay: 'stagger(100, 50)', // 100→150→200
x: 'stagger(1, .5)', // 1→1.5→2
y: 'stagger(-100, rand(100, 200))' // マイナスの値やrand()も使える
自分で用意したSVGを使用する
mo.jsの機能を使わず、自分で用意したSVGを使う場合は以下のように
class Thunder extends mojs.CustomShape {
getShape() { return '<path d="M47.31 54.73L25.81 54.73L74.19 0L52.26 40.75L74.19 40.75L25.81 100L47.31 54.73Z" />' }
getLength() { return 289.36328125; } // optional
}
mojs.CustomShape
クラスを継承したクラスを作成し、その中にSVGのパスを返すgetShape
メソッドを書きます。
また、アニメーションさせる際にstrokeDasharray
やstrokeDashoffset
に対して%の値を指定する場合、SVGのアウトラインの長さを返すgetLength
メソッドも追加で書く必要があります。
クラスを作成したら、以下のようにmojs.addShape
メソッドに作成したクラスを渡すことで使えるようになります。
mojs.addShape('thunder', Thunder);
new mojs.Shape({
shape: 'thunder',
// 略
});
実際の使用例
See the Pen mo.js - CustomShape Sample by yumetomo (@srbangs) on CodePen.
ShapeSwirlモジュール
語彙力が足りずうまく言葉で説明できないので、ShapeSwirl
モジュールを使うとどういうアニメーションができるかは以下のCodepenをご覧ください。
使い方は今まで紹介してきたShape
とほとんど同じです。
See the Pen mo.js - ShapeSwirl Sample by yumetomo (@srbangs) on CodePen.
基本的なオプションはShape
と同じですが、ShapeSwirl
用のものがいくつか増えています。
・swirlSize:揺れの大きさ(初期値は10)。
・swirlFrequency:揺れる回数(初期値は3)。
・direction:最初の揺れが左右どちらの向きから始まるか(初期値は1)。1
なら右、-1
なら左。
・pathScale:揺れを横の動きとすると、縦の移動量を調節できる(たぶんCodepenで軽く触ってみるとわかりやすいです)(初期値は1)。
・degreeShift:アニメーションの角度(初期値は0)。
Burstモジュール
複数のShape
が飛び散るようなアニメーションを実装する時に便利なモジュールです。
オプションの指定方法は以下を参考にしてみてください。
new mojs.Burst({
parent: '#parent',
left: 0,
top: 0,
radius: { 8: 64 }, // 個々のShapeの大きさではなく、飛び散る範囲に反映される
angle: { 0: 180 },
count: 7, // 飛び散らせるShapeの数
degree: 180, // 飛び散らせる範囲(この場合は0°〜180°の範囲に絞られる)
// 個々のShapeの設定はchildrenオブジェクトの中に書く
children: {
radius: 5,
fill: ['deeppink', 'cyan', 'yellow'], // 配列が使える
scale: { 1: 0 },
duration: 1000,
delay: 'stagger(200)', // staggerも使える
easing: 'quint.out'
}
});
実際の使用例
See the Pen mo.js - Burst Sample by yumetomo (@srbangs) on CodePen.
tuneメソッド
上記Codepenのこの部分で使っているtune
メソッドについて
$WRAPPER.addEventListener('click', event => {
BURST
.tune({ // ←
x: event.layerX,
y: event.layerY
})
.replay();
});
tune
メソッドを使用すると、指定のオプションの値を上書きすることができます。
今回の場合だとreplay
メソッドの実行前に、x
とy
にクリックした位置を渡して上書きしているので、クリックした位置を中心にアニメーションが実行されています。