SVGとアニメーション1〜pathに沿って移動するアニメーション

SVGとアニメーション1〜pathに沿って移動するアニメーション

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

 });