TweenMax(TweenLite)でSVGをアニメーションさせる
オブジェクトが何かしらアニメーションするサイトが去年あたりから一般的に増えてきてるように思います。
特に今年は一気に目にする機会が多くなったように思います。
あわせてcanvasやsvgを扱うサイトも増えてきて、jsで動かす対象も増えてきましたね。
今日は、javascriptのアニメーションライブラリ、「TweenMax」でsvgを動かすサンプルです。
TweenMaxを読み込んで動かせるようにする
まずはライブラリを読み込んで、動かす準備をしましょう。headにjqueryのように追加するだけでOK。
リファレンスはこちら。
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.17.0/TweenMax.min.js"></script>
SVGは全部で9種類
SVGもいろいろ種類があって、使う場面も様々。
全部で9種類あります。
- 四角形(rectタグ)
- 円(circleタグ)
- 楕円(ellipseタグ)
- 直線(lineタグ)
- 折れ線(polylineタグ)
- 多角形(polygonタグ)
- パス(pathタグ)
- 画像(imageタグ)
- 文字列(textタグ)
今回は多角形(polygon)を扱ってみます。
前提として、
- レスポンシブとして扱いたい
- ボックス要素の背景として扱いたい
- 同じボックスがあってもまとめて指定したい
としています。
polygonタグの基本
<svg>
<polygon
points="320,270 320,180 260,180 260,230 400,230"
fill="skyblue" stroke="green" stroke-width="5" />
</svg>
SVGタグの中にpolygonタグがあるのが基本。
アトリビュートとして、
- 線の太さ
- 線の色
- 塗りつぶしの色
- 多角形の頂点座標
この4つが設定できます。
多角形の頂点座標はXYをカンマ区切りで表記し、頂点ごとに半角スペースで分けるように記述すればOK。ここで指定した分の数だけの多角形になります。
ベースとなるhtml・css・jsはこちら。
レスポンシブ前提ということで、pointsはページ読み込みやリサイズ時にjsで指定したいので記述していません。
.sectionが外側となるボックス
.polygonはabsolute指定で背景にします。
html
<div class="section svgs"> <div class="polygon"> <svg id="poly00"> <polygon id="polygon00" fill="#e2ffb9"> </svg> </div><!-- /.polygon --> <h1>コンテンツ</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid quod cumque ab debitis similique autem amet, suscipit velit omnis numquam minus corporis, expedita, eaque sint adipisci voluptate? Laborum, quibusdam id!</p> </div><!-- /.section svgs --> <div class="section svgs"> <div class="polygon"> <svg id="poly01"> <polygon id="polygon01" fill="#e2ffb9"> </svg> </div><!-- /.polygon --> <h1>コンテンツ</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid quod cumque ab debitis similique autem amet, suscipit velit omnis numquam minus corporis, expedita, eaque sint adipisci voluptate? Laborum, quibusdam id!</p> </div><!-- /.section svgs -->
css
.section { position: relative; }
.polygon { position:absolute; z-index:-10; opacity: 0.4; width: 100%; height: auto; overflow: visible; }
.polygon svg { width: 100%; height: auto; overflow: visible; }
javascript
$(function() {
$(window).on('load resize',function(){
// get a parent box size
var svgBoxWidth = [];
var svgBoxHeight = [];
$(".svgs").each(function(){
svgBoxWidth.push($(this).width());
svgBoxHeight.push($(this).height());
});
var item_count = svgBoxWidth.length;
// set a viewbox's size & points
for (var i = 0; i < item_count; i++){
// set viewbox's size
var targetSvgId = "poly0" + i ;
var svgObj = document.getElementById(targetSvgId);
svgObj.setAttribute('viewBox', '0 0 '+svgBoxWidth[i]+' '+svgBoxHeight[i]);
// set points
function randompoint () {
randnum = 90 + Math.floor( Math.random() * 10 );
randnum = randnum / 100;
return randnum;
}
var basepoint = 50 + Math.floor( Math.random() * 150 );
// before animate
var pointsTL = 0+','+basepoint;
var pointsTR = Math.round(svgBoxWidth[i]*randompoint())+',0';
var pointsBR = Math.round(svgBoxWidth[i]*randompoint())+','+Math.round(svgBoxHeight[i]*randompoint());
var pointsBL = '0,'+Math.round(svgBoxHeight[i]*randompoint());
var points = pointsTL+' '+pointsTR+' '+pointsBR+' '+pointsBL;
var points_array = [ pointsTL+','+pointsTR+','+pointsBR+','+pointsBL ];
// after animate
var _pointsTL = '0,'+basepoint;
var _pointsTR = Math.round(svgBoxWidth[i]*randompoint())+',0';
var _pointsBR = Math.round(svgBoxWidth[i]*randompoint())+','+Math.round(svgBoxHeight[i]*randompoint());
var _pointsBL = '0,'+Math.round(svgBoxHeight[i]*randompoint());
var _points = _pointsTL+' '+_pointsTR+' '+_pointsBR+' '+_pointsBL;
var _points_array = [ _pointsTL+','+_pointsTR+','+_pointsBR+','+_pointsBL ];
var targetPolygon = "polygon0" + i ;
var svgPolygon = document.getElementById(targetPolygon);
svgPolygon.setAttribute("points",points);
function animateSVG(target,from,to) {
var timeLine = new TimelineMax();
timeLine
.add(TweenMax.to('#'+targetPolygon, 6, { rotation:-0.2, scale:0.95, y:'+=30px' }))
.add(TweenMax.to('#'+targetPolygon, 3, { rotation:+0.2, scale:1 , y:'-=30px' }));
timeLine.repeat(-1);
}
animateSVG(targetPolygon,points,_points);
} // end for item_count
}); // end load resize
}); // end ready
jsとしては、レスポンシブとしたいので、
loadとresize時に親ボックスである.sectionの縦横サイズを取得し、その値を
- 座標値に変更して、svgのviewboxのサイズに指定
- viewboxより小さくなるようにpointsの値をランダム値で設定
- アニメーション後の値もランダムで設定
- アニメーション処理
といった処理を.sectionごとに繰り返しています。
viewboxやpointsの値がピクセルベースなので、svg自体をcssで横幅100%にすると、スマホ時などは縦サイズが足らなくなります。それを回避したくloadとresizeに設定するようにしました。
動かしてみれば普通に動くのであんまり敷居は高くないかも。
ただちょっとハマったところがあって、
setAttributeする際の注意点
var svgObj = document.getElementById(targetSvgId);
svgObj.setAttribute('viewBox', '0 0 '+svgBoxWidth[i]+' '+svgBoxHeight[i]);
サンプルでは上記のように、要素の取得にjQueryを使っていません。
これは、
var svgObj = $(targetSvgId);
setAttributeするオブジェクトをjQueryオブジェクトにしてしまうと操作ができなくなります。
var svgObj = $(targetSvgId); var $svgObj = svgObj;
このように最初の取得はjQueryを使わず、その後jQueryオブジェクトにすることで扱えるようになります。
最初はなんで動かないのかと思いましたが、以下のページを見て解決できました。
参考:SVGをjQueryで操作する際にハマったので、書き留めておく。
attr属性を動かす際の注意点
pointsの値を動かすには注意が必要です。
TweenMaxに指定する値を記述してもアニメーションはしません。
attr属性のものを動かすには、TweenMaxの追加プラグイン「AttrPlugin」が必要です。
しかも、これを追加しても値として設定できるのは「数字」が指定されているプロパティのみ。
pointsのようにカンマなどが含まれているものは動かすことができません。
こういったものを動かすには、.onUpdateを利用する必要があります。
参考:TweenLiteを使用して SVG polygon(polyline) を任意の形状へ変形アニメーションさせる方法
海外のAttrPluginへの反応も調べてみましたが、けっこう問題があるようです。
アップデートで良くなるといいですね。