本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100
ローディング

ローディングのバーと数字が 0 → 100% に進む

デモ

仕様

動作
DOM
種類
レシピ
動作確認
three 0.186.0・gsap 3.15.0
公開日

しくみと調整

読み込みの進み具合を、ただの数値としてトゥイーンし、そこからバーの scaleX と数字を読み取って描きます。バーと数字が同じ値から生まれるので、両者がずれません。数字には桁の幅をそろえる指定を添えて、増えるあいだガタつかないようにしています。

実際の読み込み率に合わせるには、届いた割合を set に渡します。内部では 0.4 秒かけてその値までなめらかに動くので、飛び飛びに届く進捗でも滑らかに見えます。読み込みが終わったら done を呼ぶと、100% まで進んだあと少し間を置いてから消えます。

バーの見える幅は scaleX で表し、幅そのものは書き換えません。幅の指定を直接動かすと、再計算のたびに描画がガクつきます。数字と % を1行に並べるときは、横並びの箱にすると単位が下の段へ落ちるので、ひとつの段落の中に続けて書いています。

バーの幅や色は CSS 変数で変えられます。ファイルの受信や画像の先読みのように、残りが数で分かる待ちに向いています。数がわからない処理には、終わりの見えない動きのローダーのほうが自然です。

コード

HTML
<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>

CSS
.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);
}

JavaScript
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"));

インタラクションデザインについて、
お気軽にご相談ください。

ここに並べているのは、Web サイトの中で動く UI のサンプルです。新しく作るサイトに組み込むことも、いま公開中のサイトに、ボタンやヒーロー、スクロールの演出だけを足すこともできます。設計から実装までの進め方はWeb サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。