SVGの画像の切り抜きとsnap.svgを使ったアニメーション

こんにちは、中村です。
SVGについて、あまり勉強してこなかったので、
今回は気になっていた写真の切り抜きとアニメーションを試したいと思います。

DEMO

作ったのはこちら。

See the Pen SVGの画像の切り抜きとsnap.svgを使ったアニメーション/a> by Nakamura (@takayo-nakamura) on CodePen.

星の形に写真を切り抜く

クリッピングパスを使って写真を切り抜いています。 CSSのプロパティにもclip-pathはありますが、IEが対応していないため、 今回はSVGのclip-path属性を使って切り抜きました。

クリッピング用パス

SVGのパスの記述を<clipPath id="clip">で囲み、idを付けます。
このSVG自体は表示させたくないため、CSSで非表示にします。
display:none;では効果がなくなってしまうため、 position: absolute;にしつつ、幅と高さをなくすことで非表示にします。

<!-- クリッピング用パス -->
<svg xmlns="http://www.w3.org/2000/svg" class="clipPath">
  <clipPath id="clip">
    <polygon points="111.06 0 145.38 69.54 222.12 80.69 166.59 134.82 179.7 211.25 111.06 175.17 42.42 211.25 55.53 134.82 0 80.69 76.74 69.54 111.06 0" />
  </clipPath>
</svg>
// クリッピング用パス
.clipPath {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
}

写真をSVGで表示する

  <svg id="svgPhoto" class="star_photo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222 211">
    <image xlink:href="https://picsum.photos/500/500" width="100%" height="100%" preserveAspectRatio="xMidYMid slice" clip-path="url(#clip)" />
  </svg>

clip-path属性には先ほど記述したクリッピング用パスのidを指定します。

clip-path="url(#clip)"

これで写真の切り抜きができました。

アニメーションを加える

次にアニメーションも加えたいと思います。
星の淵に沿って線が描画され、その後に写真が表示されるようにしたいと思います。

写真を表示したSVGの上に、同じ形のSVGを重ねます。

  <svg id="svgCover" class="star_cover" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 222 211">
    <polygon points="111.06 0 145.38 69.54 222.12 80.69 166.59 134.82 179.7 211.25 111.06 175.17 42.42 211.25 55.53 134.82 0 80.69 76.74 69.54 111.06 0" />
  </svg>

線の描画はCSSでstrokeを使います。
塗りはfillです。

線の描画アニメーションの仕組みは、破線を使って行います。
破線の間隔を星一周分にし、開始位置の数値を合わせると、線のない状態になります。
開始位置を0に向けて下げていくことで、描画されているように見えます。

    stroke-dasharray: 1000px; // 破線の間隔
    stroke-dashoffset: 1000px; // svgパスの始まりの位置

CSSなのでanimationでも動かすことが可能ですが、 IEはanimationでstroke-dashoffsetの値を変えることができないようなので、snap.svgというjavascriptのプラグインを使って動かします。

プラグイン:Snap.svg - Home

snap.svgを使う

snap.svgのjsを読み込みます。
あとはアニメーションさせたい要素を取得し、それに対してアニメーションを加えていきます。
写真を先に非表示にしているのは、そうしないと薄ら見えてしまうからです。

<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js"></script>
const svgStar = Snap('#svgStar'); // 2つのsvgの親要素を取得
const svgPhoto = svgStar.select('#svgPhoto'); //その中の写真のsvgを取得
const svgCover = svgStar.select('#svgCover'); //その中の上に重ねるsvgを取得
svgPhoto.animate({ opacity: 0 }, 0); //写真のsvgを非表示にする
svgCover.animate({ 'stroke-dashoffset': 0 }, 1800, function () { //上に重ねるsvgのパスの開始位置を、1800msかけて0にする
  svgPhoto.animate({ opacity: 1 }, 0); //写真のsvgを表示する
  svgCover.animate({ 'fill-opacity': 0 }, 800);//上に重ねるsvgの塗りを800msかけて非表示にする
});

最後に

最初は星ではなくてsvgのtextで文字で作っていたのですが、
stroke-dashoffsetのアニメーションがうまく動かなかったので諦めました。
またどこかで試してみたいと思います。