今回はは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)'; });