デモ
デモを読み込めませんでした。
仕様
- 用途
- ローディング
- 動作
- DOM
- 種類
- レシピ
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
濃い線が輪の上を進んでいき、その後ろに、少しずつ薄くなった線が尾を引いて続きます。ただ、図形そのものは回していません。30°おきに放射状に並べた12本の線の濃さを、順番にトゥイーンしているだけです。1周は1秒で、隣の線とは、1周を本数で割ったぶんずつずらして薄れさせています。
濃さは CSS の opacity ではなく、SVG の stroke-opacity で変えることにしました。opacity を動かすと、ブラウザによっては線ごとに合成用のレイヤーが作られることがあります。stroke-opacity なら、線を描き直すだけで済みます。アニメーションは、最初から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"));