本文へスキップ
HELLO!
HIGASHIYUKI®.COM
0   /   100

ローディング

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

デモ

仕様

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

しくみと調整

バーの伸びと数字の増え方が、最後まで少しもずれずに進みます。進み具合をひとつの数値としてトゥイーンし、バーの長さと数字の両方を、その同じ値から描いているからです。数字には tabular-nums を付けて桁ごとの文字幅をそろえ、増えていくあいだに表示が横に揺れないようにしました。

バーの長さを変えるなら幅を直接動かしたくなりますが、それだと値が変わるたびにレイアウトの再計算が走り、描画がガクつきます。要素の幅はそのままにして、見える長さを scaleX で伸ばしています。

数字と「%」は、ひとつの段落の中に続けて書きました。横並びにするための要素で包むと、単位の「%」だけが下の段へ落ちてしまいます。

実際の読み込み率は set に渡します。内部では0.4秒かけてその値まで動かすので、進捗の届き方が不規則でも、バーと数字は滑らかに動きます。読み込みが終わって done を呼ぶと、100%まで進んだあと少し間を置いてから消えます。バーの幅と色は 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 サイト制作、端末ごとの見え方や表示速度の考え方はレスポンシブウェブサイトのページにまとめています。映像として作る動きをお探しの場合はモーショングラフィックスをご覧ください。