今回ははSVGのpathに沿ってDOM要素をアニメーションさせる方法です。
SVGのpathに沿ってアニメーションさせる
CodeGridの記事が元ネタです。(有料ですがフロントエンドの方にはお勧めのサイトです。)
シンプルなサンプル
このサンプルのアニメーションの仕組みは刻々と進む時間に対してpathの特定の長さを指定して、getPointAtLengthで現在のpath長さの座標を導き出します。その座標を円の中心座標にすることで移動をさせています。
具体的にはタイマーはsetIntevalを使用せずに、requestAnimationFrame()を使うことで代用しています。
requestAnimationFrameはブラウザの再描画の準備が整ったら次の関数を読み込みます。読み込む速度はブラウザに依存しています。そのため、経過時間を取得する処理を追記する必要があります。今回は変数elapsedTimeに現時刻から経過時間を算出して使用しています。
HTMLコード
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="-10 -10 400 400"><path id="myPath" d="M25.3,302.4C420.51,356,409,23.6,212.08,26.9c-227.71,3.81-162.65,349,167.47,283.95" style="stroke:#12E4FF;fill:none"/> <circle id="myPoint" r="10" fill="#FF0B68"></circle> </svg>
JavaScriptコード
var startTime = Date.now(), // 開始時の現時刻
elapsedTime = 0, // 経過時間
duration = 5000, // アニメーション継続時間
myPath = document.querySelector( '#myPath' ),//pathの情報を取得
pathLength = myPath.getTotalLength(),//pathの全長を取得
myCircle = document.querySelector( '#myPoint' ),//移送する円の情報を取得
progress = 0;//時間調整初期値
var move = function () {
requestAnimationFrame( move );
// 時計を使って経過時間をアップデート
elapsedTime = Date.now() - startTime;
// 継続時間(duration)内において、経過時間(elapsedTime)に対する進行度(progress)を求める
var progress = ( elapsedTime % duration ) / duration;//進行度
var point = myPath.getPointAtLength( pathLength * progress );
myCircle.setAttribute( 'cx', point.x );
myCircle.setAttribute( 'cy', point.y );
};
move();
パララックスとして活用
サンプル
先のサンプルをスクロール量に基づいて移動するパララックスタイプに変更したものです。
pathの長さと最大スクロール量をJavaScriptで取得して、pathLength/scrollMaxとすることでちょうどスクロールが終了した時点でpathの終点に移動できるように変数speedを設定しました。
尚、最大スクロール量取得は「window.scrollMaxY」ではChromeなどで使えません。「document.documentElement.getBoundingClientRect().height – window.innerHeight」で代用しています。
サンプルは解りやすいようにpathに水色を着けていますが、実際はstroke=”none”として透明にしておきます。
HTMLコード
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 980 1500"> <path id="myPath" d="M104.92,55.17c47.46,27.12,696.61,172.88,718.64,313.56S232,480.59,184.58,670.42,691.36,955.17,740.51,1145s-522,378-616.95,384.75" fill="none" stroke="#0066ff" /> <circle id="myPoint" cx="104" cy="55" r="10" fill="red"></circle> </svg>
JavaScriptコード
var myPath = document.querySelector( '#myPath' );
var pathLength = myPath.getTotalLength();
var scrollMax = document.documentElement.getBoundingClientRect().height - window.innerHeight;
var myCircle = document.querySelector( '#myPoint' );
var speed = pathLength/scrollMax;
$(window).on('scroll', function() {
var progress = $(this).scrollTop()*speed;
var point = myPath.getPointAtLength(progress);
myCircle.setAttribute( 'cx', point.x );
myCircle.setAttribute( 'cy', point.y );
if ( progress >= pathLength ) {
progress = 0;
} else {
progress += spead;
}
});
imgタグで挿入したSVGをアニメーション
サンプル
パララックスタイプのサンプルをさらにimgタグで挿入した画像(今回はSVG画像、どんな形式のものでもOK)をアニメーションしました。これはDOM要素にアニメーションをさせることも可能な証拠です。つまりどんな要素でもpathに沿ってアニメーションできるということです。
HTMLコード
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 980 1500"> <path id="myPath" d="M104.92,55.17c47.46,27.12,696.61,172.88,718.64,313.56S232,480.59,184.58,670.42,691.36,955.17,740.51,1145s-522,378-616.95,384.75" fill="none" stroke="none" /> </svg> <div class="wrapper"> <img id="dog" src="dog.svg" width="150" height="150" alt=""> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt in fugiat culpa fugit deleniti maiores consequatur ipsam sunt quaerat, eligendi qui assumenda magnam aliquam totam ea dolore amet, sit repellendus!</p> </div>
CSSコード
.wrapper{
position:absolute;
top:0;
}
#dog{
position:absolute;
}
JavaScriptコード
var scrollMax = document.documentElement.getBoundingClientRect().height - window.innerHeight,
myPath = document.querySelector( '#myPath' ),
myPath = document.querySelector( '#myPath' ),
pathLength = myPath.getTotalLength(),
myPoint = document.querySelector( '#dog' ),
speed = pathLength/scrollMax;
$(window).on('scroll', function() {
var progress = $(this).scrollTop()*speed;
var point = myPath.getPointAtLength( progress );
var pointPrevious = myPath.getPointAtLength(pathLength * progress - 1 );
var lengthY = point.y - pointPrevious.y;
var lengthX = point.x - pointPrevious.x;
var angle = Math.atan( lengthY / lengthX ) * ( -90 / Math.PI );
myPoint.style.webkitTransform = ' translate(' + point.x + 'px,' + point.y + 'px) rotate(' + angle + 'deg)';
myPoint.style.mozTransform = ' translate(' + point.x + 'px,' + point.y + 'px) rotate(' + angle + 'deg)';
myPoint.style.msTransform = ' translate(' + point.x + 'px,' + point.y + 'px) rotate(' + angle + 'deg)';
myPoint.style.transform = ' translate(' + point.x + 'px,' + point.y + 'px) rotate(' + angle + 'deg)';
});
