HOME / BLOG
BLOG

SVGの線を描画するアニメーションの作り方

デフォルト画像

SVGの線が少しずつ描かれていくアニメーションは、ロゴ、チェックマーク、署名、ローディングなどに使えます。基本はstroke-dasharraystroke-dashoffsetで線を隠し、offsetを0へ戻すだけです。

この記事では、線の長さをJavaScriptで測らずに扱えるpathLength="1"を使い、CSS中心で再利用できる実装を作ります。

仕組み

stroke-dasharrayは破線のパターン、stroke-dashoffsetはその開始位置を指定します。線全体と同じ長さの破線を作り、同じ量だけずらすと線が見えなくなります。

.draw-line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.is-visible .draw-line {
  stroke-dashoffset: 0;
}

pathLength=”1″で長さを正規化する

SVGのpathへpathLength="1"を指定すると、線の全長を1としてdash値を扱えます。複雑なパスでも実寸を調べる必要がありません。

<svg viewBox="0 0 120 120" aria-hidden="true">
  <path
    class="draw-line"
    pathLength="1"
    d="M20 62l25 25 55-64"
  ></path>
</svg>

基本のCSSアニメーション

.draw-line {
  fill: none;
  stroke: currentColor;
  stroke-width: 8;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: draw 1s ease forwards;
}

@keyframes draw {
  to {
    stroke-dashoffset: 0;
  }
}

複数の線を順番に描く

複数パーツはanimation-delayをずらします。HTMLの順番と視線の流れを合わせると自然に見えます。

.logo-line:nth-child(1) { animation-delay: 0s; }
.logo-line:nth-child(2) { animation-delay: .25s; }
.logo-line:nth-child(3) { animation-delay: .5s; }

スクロールで画面に入った時に開始する

IntersectionObserverでクラスを付ければ、記事内のSVGが画面に入った時だけ描画できます。

const target = document.querySelector('.draw-icon');

const observer = new IntersectionObserver(([entry], observer) => {
  if (!entry.isIntersecting) return;

  entry.target.classList.add('is-drawing');
  observer.unobserve(entry.target);
}, {
  threshold: 0.4,
});

observer.observe(target);

CodePen埋め込み用エリア

SVGの線を順番に描画するデモ
ここにCodePenのEmbedコードを貼り付けます。Pen作成後、このブロックと埋め込みタグを差し替えてください。

以下をCodePenへ貼り付けると、円、チェック、波線を順番に描画できます。「DRAW AGAIN」でアニメーションを再実行できます。

HTML

<main class="line-demo">
  <button class="draw-button" type="button">DRAW AGAIN</button>

  <svg class="draw-icon" viewBox="0 0 320 180" role="img" aria-label="線が描画されるSVG">
    <circle class="draw-line draw-line--circle" pathLength="1" cx="80" cy="90" r="48"></circle>
    <path class="draw-line draw-line--check" pathLength="1" d="M52 91l20 20 39-46"></path>
    <path class="draw-line draw-line--wave" pathLength="1" d="M160 110c28-82 54 42 82-30s42 20 58-36"></path>
  </svg>
</main>

CSS

* {
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  margin: 0;
  display: grid;
  place-items: center;
  padding: 24px;
  background: #111;
  color: #fff;
  font-family: system-ui, sans-serif;
}

.line-demo {
  width: min(100%, 720px);
  text-align: center;
}

.draw-icon {
  display: block;
  width: 100%;
  margin-top: 24px;
  overflow: visible;
  fill: none;
  stroke: #caff00;
  stroke-width: 8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.draw-line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.draw-icon.is-drawing .draw-line {
  animation: draw-line 1.25s ease forwards;
}

.draw-icon.is-drawing .draw-line--check {
  animation-delay: .35s;
}

.draw-icon.is-drawing .draw-line--wave {
  animation-delay: .7s;
}

.draw-button {
  min-height: 48px;
  padding: 0 24px;
  border: 2px solid #caff00;
  border-radius: 999px;
  background: transparent;
  color: #caff00;
  font-weight: 900;
  cursor: pointer;
}

.draw-button:hover,
.draw-button:focus-visible {
  background: #caff00;
  color: #111;
}

@keyframes draw-line {
  to {
    stroke-dashoffset: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .draw-line {
    stroke-dashoffset: 0;
  }

  .draw-icon.is-drawing .draw-line {
    animation: none;
  }
}

JS

const icon = document.querySelector(".draw-icon");
const button = document.querySelector(".draw-button");

function draw() {
  icon.classList.remove("is-drawing");
  void icon.getBoundingClientRect();
  icon.classList.add("is-drawing");
}

button.addEventListener("click", draw);
draw();

よくある失敗

  • fillが残っている:線だけを見せたい場合はfill: noneにします。
  • dash値が線の長さと合わない:pathLength="1"で正規化すると管理しやすくなります。
  • アニメーション後も線が戻る:animation-fill-mode: forwardsを指定します。
  • 再実行しても動かない:クラスを外した後にレイアウトを一度確定させてから再付与します。
  • 常時繰り返す:視線を奪いやすいため、基本は一度だけの演出にします。
  • 動きを減らす設定を無視する:prefers-reduced-motionでは最初から線を表示します。

まとめ

SVG線描画は、pathLength="1"stroke-dasharray: 1stroke-dashoffsetの3点を押さえると短いコードで実装できます。複数パーツはdelay、表示タイミングはIntersectionObserverで制御できます。

参考資料