デモ
デモを読み込めませんでした。
仕様
- 用途
- ローディング
- 動作
- DOM
- 種類
- レシピ
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
12本の線を 30° ずつ放射状に並べ、図形そのものは回さずに、線ごとの濃さを順にトゥイーンして回っているように見せます。濃さは CSS の opacity ではなく SVG の stroke-opacity で変えていて、要素ごとに合成レイヤーを作らず、線の中だけで描き直されます。
1周は 1 秒で、線どうしは 12 分の 1 周期ずつ遅れて薄れるので、途切れずに尾を引いて見えます。開始した直後から一部の線しか回っていない立ち上がりは避けたいので、最初から1周先の状態を描いています。
薄れ方には power1.out を使っていて、濃い状態を少し保ってから素早く薄れます。等速で薄れさせると、先頭の線がぼやけて読みにくくなります。
線の本数や太さ、色を変えるだけで、大きさの違う場所に使い回せます。投稿中の画面中央の表示や、押したボタンの中の小さな表示など、複数の場所で同じ部品を使えます。
コード
<div class="segment-loader" role="status">
<svg class="segment-loader__svg" viewBox="-50 -50 100 100" aria-hidden="true"></svg>
<span class="segment-loader__text">読み込み中</span>
</div>
.segment-loader {
--ground: #f2f1ed; /* 地の色 */
--ink: #262626; /* 線の色 */
--size: 96px; /* SVG の一辺 */
--width: 8; /* 線の太さ(viewBox 100 の中の値) */
position: fixed;
inset: 0;
z-index: 10;
display: grid;
place-items: center;
background: var(--ground);
}
.segment-loader__svg {
display: block;
width: var(--size);
height: var(--size);
overflow: visible;
}
.segment-loader__svg line {
stroke: var(--ink);
stroke-width: var(--width);
stroke-linecap: round;
}
/* 読み上げにだけ届く文言。画面には出さない */
.segment-loader__text {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
import { gsap } from "gsap";
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const SVG_NS = "http://www.w3.org/2000/svg";
const COUNT = 12;
const PERIOD = 1; // 1 周の時間(秒)
const MIN = reduceMotion ? 0.55 : 0.16; // いちばん薄い線の濃さ。動きを減らす設定では濃淡の差を小さくする
function createLoader(root) {
const svg = root.querySelector(".segment-loader__svg");
// 内側の半径 24・外側の半径 42 の線を 30° ずつ並べる(丸い線端のぶん外へ半分の太さだけはみ出す)
const segments = Array.from({ length: COUNT }, (_, i) => {
const line = document.createElementNS(SVG_NS, "line");
line.setAttribute("x1", "0");
line.setAttribute("y1", "-24");
line.setAttribute("x2", "0");
line.setAttribute("y2", "-42");
line.setAttribute("transform", `rotate(${i * (360 / COUNT)})`);
svg.append(line);
return line;
});
// 図形は回さず、線ごとの濃さを 1 → MIN へ順にトゥイーンして、回っているように見せる
const spin = gsap.fromTo(
segments,
{ attr: { "stroke-opacity": 1 } },
{ attr: { "stroke-opacity": MIN }, duration: PERIOD, ease: "power1.out", stagger: { each: PERIOD / COUNT, repeat: -1 } }
);
spin.time(PERIOD); // 立ち上がりを飛ばし、全部の線が回っている状態から始める
return {
// 読み込みが終わったら呼ぶ
done() {
gsap.to(root, {
autoAlpha: 0,
duration: reduceMotion ? 0 : 0.3,
onComplete: () => {
spin.kill();
root.remove();
},
});
},
};
}
// 例: 投稿中の HUD や、押したボタンの中にも同じ形で使える。終わったら loader.done()
export const loader = createLoader(document.querySelector(".segment-loader"));