デモ
デモを読み込めませんでした。
仕様
- 用途
- ローディング
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- DrawSVGPlugin
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
円とチェックマークを 1 本の線として描き、描き終えたところへ面をはめ込みます。DrawSVGPlugin を使うと stroke-dasharray の値を自分で計算しなくて済み、0% から100% まで指定するだけで線が伸びていきます。
円は3時の方向から時計回りに描かれる仕様なので、rotate(-90) を掛けて12時から描き始めています。線の色は面より明るくしてあり、差し込んだ面の上でもチェックマークがはっきり読めます。
丸くした線の端は、長さを0% まで縮めても先端に点として残ってしまうので、描き終わるまでは隠しておき、描き始める瞬間に表示へ切り替えています。
面の色や線の太さ、描く速さは差し替えられます。送信の完了やタスクの完了など、結果をひと目で伝えたい場面に向いています。
コード
<!-- 完了バッジ。線を描き終えたら面を差し込む。SVG は飾りなので aria-hidden -->
<div class="check-badge">
<svg class="check-badge__svg" viewBox="0 0 200 200" aria-hidden="true">
<circle class="check-badge__disk" cx="100" cy="100" r="72"></circle>
<circle class="check-badge__ring" cx="100" cy="100" r="82" transform="rotate(-90 100 100)"></circle>
<path class="check-badge__check" d="M70 102 L90 123 L134 77"></path>
</svg>
<!-- 読み上げ用。最初から文が入っていると読み上げられないので空にしておき、show() が data-message の文を入れる -->
<span class="check-badge__text" role="status" data-message="完了しました"></span>
</div>
.check-badge {
--size: 200px; /* バッジの直径 */
--ink: #eeece6; /* 円とチェックの線の色 */
--success: #4f8a64; /* 差し込む面の色。線より明るくして、面の上のチェックを読めるようにする */
--ground: #111111; /* 地の色 */
display: grid;
place-items: center;
min-height: 60vh;
padding: 96px 6vw;
background: var(--ground);
}
.check-badge__svg {
display: block;
width: var(--size);
height: var(--size);
}
/* 描き終えるまで隠しておく。JS が show() で見せる */
.check-badge__ring,
.check-badge__check,
.check-badge__disk {
visibility: hidden;
}
.check-badge__disk { fill: var(--success); }
.check-badge__ring {
fill: none;
stroke: var(--ink);
stroke-width: 7;
stroke-linecap: round;
}
.check-badge__check {
fill: none;
stroke: var(--ink);
stroke-width: 9;
stroke-linecap: round;
stroke-linejoin: round;
}
/* 読み上げにだけ届く文言。画面には出さない(文は show() が入れる) */
.check-badge__text {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
import { gsap } from "gsap";
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin";
gsap.registerPlugin(DrawSVGPlugin);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
function createBadge(root) {
const ring = root.querySelector(".check-badge__ring");
const check = root.querySelector(".check-badge__check");
const disk = root.querySelector(".check-badge__disk");
const text = root.querySelector(".check-badge__text");
const SPEED = reduceMotion ? 20 : 1; // 動きを減らす設定では、ほぼ一瞬で切り替える
gsap.set([ring, check], { drawSVG: "0%" });
gsap.set(disk, { scale: 0, transformOrigin: "50% 50%" });
return {
// 完了したら呼ぶ。円 → チェック → 面の順に描き込む
show() {
return gsap
.timeline()
.set([ring, check, disk], { visibility: "visible" })
.to(ring, { drawSVG: "100%", duration: 1.1, ease: "power2.inOut" })
.to(check, { drawSVG: "100%", duration: 0.55, ease: "power2.out" }, 0.88)
.to(disk, { scale: 1, duration: 0.66, ease: "back.out(1.6)" }, 1.21)
// 読み上げの文は、チェックを描き終えた所で入れる(role="status" は、後から入った文だけを読み上げる)
.call(() => (text.textContent = text.dataset.message), null, 1.43)
.timeScale(SPEED);
},
// バッジを消す。線を終点側へ縮め、面を引っ込めてから取り除く
hide() {
return gsap
.timeline({ onComplete: () => root.remove() })
.to([ring, check], { drawSVG: "100% 100%", duration: 0.66, ease: "power2.in" })
.to(disk, { scale: 0, duration: 0.55, ease: "power2.in" }, "<0.11")
.timeScale(SPEED);
},
};
}
export const badge = createBadge(document.querySelector(".check-badge"));
// 使い方の例: ページの読み込みが終わったら、完了を見せる。
// 案件では、送信が成功した所などで badge.show() を呼び、閉じるかタイマーで badge.hide() を呼ぶ
if (document.readyState === "complete") badge.show();
else window.addEventListener("load", () => badge.show(), { once: true });