デモ
デモを読み込めませんでした。
仕様
- 用途
- ローディング
- 動作
- DOM
- 種類
- レシピ
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
読み込みの進み具合を、ただの数値としてトゥイーンし、そこからバーの scaleX と数字を読み取って描きます。バーと数字が同じ値から生まれるので、両者がずれません。数字には桁の幅をそろえる指定を添えて、増えるあいだガタつかないようにしています。
実際の読み込み率に合わせるには、届いた割合を set に渡します。内部では 0.4 秒かけてその値までなめらかに動くので、飛び飛びに届く進捗でも滑らかに見えます。読み込みが終わったら done を呼ぶと、100% まで進んだあと少し間を置いてから消えます。
バーの見える幅は scaleX で表し、幅そのものは書き換えません。幅の指定を直接動かすと、再計算のたびに描画がガクつきます。数字と % を1行に並べるときは、横並びの箱にすると単位が下の段へ落ちるので、ひとつの段落の中に続けて書いています。
バーの幅や色は CSS 変数で変えられます。ファイルの受信や画像の先読みのように、残りが数で分かる待ちに向いています。数がわからない処理には、終わりの見えない動きのローダーのほうが自然です。
コード
<div class="bar-loader">
<div class="bar-loader__panel" role="progressbar" aria-label="読み込みの進み具合"
aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<p class="bar-loader__label" aria-hidden="true"><span class="bar-loader__digits">0</span>%</p>
<div class="bar-loader__track">
<div class="bar-loader__bar"></div>
</div>
</div>
</div>
.bar-loader {
--width: 512px; /* バーの幅(狭い画面では左右に 16px ずつ残して縮む) */
--ink: #eeece6; /* 数字とバーの色 */
--line: rgba(238, 236, 230, 0.14); /* 溝の色 */
--ground: #111111; /* 地の色 */
position: fixed;
inset: 0;
z-index: 10;
display: grid;
place-items: center;
background: var(--ground);
color: var(--ink);
}
.bar-loader__panel {
width: min(var(--width), calc(100% - 32px));
}
.bar-loader__label {
margin: 0 0 24px;
font-size: 64px;
font-weight: 800;
line-height: 1;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
}
.bar-loader__track {
height: 6px;
border-radius: 3px;
background: var(--line);
overflow: hidden;
}
.bar-loader__bar {
height: 100%;
background: var(--ink);
transform-origin: 0 50%;
transform: scaleX(0);
}
import { gsap } from "gsap";
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const SPEED = reduceMotion ? 12 : 1; // 動きを減らす設定では、ほぼ一瞬で進める
function createLoader(root) {
const panel = root.querySelector(".bar-loader__panel");
const bar = root.querySelector(".bar-loader__bar");
const digits = root.querySelector(".bar-loader__digits");
const state = { value: 0 };
const draw = () => {
bar.style.transform = `scaleX(${state.value / 100})`;
digits.textContent = String(Math.round(state.value));
panel.setAttribute("aria-valuenow", String(Math.round(state.value)));
};
return {
// 進み具合を 0〜1 で渡す。飛び飛びで届く値でも、トゥイーンを挟めば滑らかに進む
set(progress) {
const to = Math.min(1, Math.max(0, progress)) * 100;
gsap.to(state, { value: to, duration: 0.4 / SPEED, ease: "power2.out", onUpdate: draw });
},
// 読み込みが終わったら呼ぶ。100% まで進め、少し間を置いてから浮きながら消える
done() {
return gsap
.timeline({ onUpdate: draw, onComplete: () => root.remove() })
.to(state, { value: 100, duration: 0.4, ease: "power2.out" })
.to(panel, { y: -14, duration: 0.4, ease: "power2.in" }, "+=0.3")
.to(root, { autoAlpha: 0, duration: 0.4, ease: "power2.in" }, "<")
.timeScale(SPEED);
},
};
}
// 例: 受け取った量を loader.set(loaded / total) で渡し、読み終わったら loader.done()
export const loader = createLoader(document.querySelector(".bar-loader"));