GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

スプラッシュ画面 9種 — 読み込みに結ぶロゴのワイプから円形の開きとヘッダーへの引き継ぎまで

スプラッシュ画面は読み込みをどうやってロゴと数字に変えるのでしょうか。01番は読み込み率ひとつでロゴのマスク幅を決め、02番は最短表示時間と完了時刻の大きいほうで退場し、06番はいちばん遠い角までの距離で円形の開きを終えます。

スプラッシュ画面は読み込みを隠す絵ではなく、読み込み率と時間表の二つの値からロゴも数字も開き方もすべて計算する仕組みです — スプラッシュ画面を作るときはどの値が画面を動かすかを決めるところから始まります

自動再生 · タイルを押すとその項目へ · すべて1つのzip

九つの場面は、同じモデルひとつの上で動きます。画面は480×300のキャンバスひとつで、読み込み率は種で決まる仮想リソースの表 makeLoad から出て(02だけは表を使わず、二回の訪問の固定の読み込み時間を持ちます)、時計は場面の時計 sceneT ひとつです。ロゴも数字も開き方もこの二つの値から計算した画面にすぎないので、実際のサイトでは率の一行を本物の値に差し替えるだけで済みます。

並び順は読み込みの表示から場面の切り替えへ広がります。01・02・03は、ロゴのマスクと最短表示時間とパーセント表示を、04・05・06は、幕と文字と円形の開きを、07・08・09は、リングとヘッダーへの引き継ぎとランディングのイントロを扱います。

スプラッシュ画面 アニメーション 9種 — 読み込みに結ぶロゴのワイプ・最短表示時間を守る退場・追いかけるパーセント表示・二枚の幕・文字バネのワードマーク・円形の開き・資源の重みのリング・ヘッダーへ飛ぶロゴ・ランディングのヒーローイントロ

九つの場面の面積と動き

場面 累計面積 動いたフレーム 強度の平均
01 ロゴのワイプ 6.0% 23/23 48.6
02 最短表示の退場 22.8% 23/23 117.2
03 パーセント表示 8.3% 23/23 105.2
04 二枚の幕 30.9% 16/23 67.6
05 文字のバネ 14.1% 15/23 112.0
06 円形の開き 26.8% 6/23 63.3
07 リングの区間 6.9% 23/23 81.8
08 ロゴの飛行 31.1% 9/23 65.6
09 ランディングのイントロ 11.1% 23/23 61.8

画面を大きく覆う08が31.1%でいちばん広く、04が30.9%で続き、ロゴひとつが満ちる01は6.0%でいちばん狭くなっています。強度の平均は、ちらつく02が117.2で最も高く、01が48.6で最も低い値です。

動いたフレームが23/23なのは01・02・03・07・09の五つで、04は16/23、05は15/23です。06は6/23、08は9/23にとどまるのは、開きが0.9秒で終わり、飛行と入れ替えが2.75秒から3.75秒のあいだに終わると本文が止まるからです。

01読み込み率に合わせてロゴが満ちるワイプ

読み込み率 LOAD_FRAC ひとつが MASK_W(f) = f × (LOGO_S 74 + EDGE_SOFT 18) のマスク幅になるので、率が止まればワイプも止まります。端の18 pxは6段階のアルファでやわらかく切り、時間だけで満たす右の盤は3.6秒に実際の0.303ではなく0.457を指して、最大20.8ポイント(2.90秒)ずれます。アプリやサイトの最初の画面で、読み込みの進みをロゴで見せたいときに使います。

読み込み率に合わせてロゴが満ちるワイプ
新しいタブで開く

01 · 読み込み率に合わせてロゴが満ちるワイプ — 計測 累積面積 6.018% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

LOAD_FRACMASK_WEDGE_SOFT
    function build() {
        makeLoad(SEED_BASE + (lap % 3), 8, LOAD_START, LOAD_SPAN);
    }
    function update(dt) { if (dt < 0) {
        return;
    } }
    function LOAD_FRAC(t) { return loadFrac(t); }
    function TIME_FRAC(t) { return clamp((t - LOAD_START) / LOAD_SPAN, 0, 1); }
    function MASK_W(f) { return maskW(LOGO_S, EDGE_SOFT, f); }

02最短表示時間を守る退場の時刻

退場の開始 EXIT_START は、読み込み完了の時刻と最短表示時間 MIN_HOLD 0.6秒の大きいほうです。速い訪問は読み込みが0.35秒で終わり、完了と同時に退場する右の盤は350 msで消えて FLASH_MS の500 msを下回りますが、左は600 msまでロゴを留めます。遅い訪問(2.4秒)は左右とも2400 msなので、この決まりが効くのは速い訪問だけです。

最短表示時間を守る退場の時刻
新しいタブで開く

02 · 最短表示時間を守る退場の時刻 — 計測 累積面積 22.769% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

MIN_HOLDEXIT_STARTEXIT_MS
    function visitOf(t) { return t >= VISIT_AT[1] ? 1 : 0; }
    // 訪問の開始から測った時刻
    function localT(t) { return t - VISIT_AT[visitOf(t)]; }
    function LOAD_DONE(t) { return VISIT_LOAD[visitOf(t)]; }
    // 退場の開始時刻 — 左は最短表示を守り、右は読み込みが終わるとすぐ
    function EXIT_START(t, keep) { return keep ? Math.max(LOAD_DONE(t), MIN_HOLD) : LOAD_DONE(t); }
    function splashAlpha(t, keep) { return 1 - ease((localT(t) - EXIT_START(t, keep)) / (EXIT_MS / 1000)); }

03実際の値をなめらかに追うパーセント表示

目標の割合はリソースがまるごと届いたときだけ上がり、左の数字は残りの差の一部ずつ追いかけます(CATCH_RATE 5)。一回の到着の95%まで0.60秒で届くのに対し、目標をそのまま出す右は最後の270 KBの塊で一度に25ポイント跳ね、3.54秒から5.00秒までの1.46秒は止まったままです。進捗の数字を飛ばさずに見せたい読み込み画面で使います。

実際の値をなめらかに追うパーセント表示
新しいタブで開く

03 · 実際の値をなめらかに追うパーセント表示 — 計測 累積面積 8.297% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

CATCH_RATEDISPLAY_PCTARRIVED_KB
    function ARRIVED_KB(t) { return arrivedKb(t); }
    function target(t) { return ARRIVED_KB(t) / totalKb; }
    // 残りの差 × (1 − e^(−rate·dt)) だけ近づく — 目標が同じ間は、dt を細かく分けても一度に進めても同じ値
    function update(dt) {
        const tg = target(sceneT);
        if (Math.abs(tg - lastTarget) > 1e-9) {
            lastTarget = tg;
            lastChange = sceneT;
        }
        disp += (tg - disp) * (1 - Math.exp(-CATCH_RATE * dt));
        while (nextSample <= sceneT) {
            hist.push([nextSample, target(nextSample), disp]);
            nextSample += SAMPLE_S;
        }
    }

04二枚の幕が割れて本文が現れる切り替え

カーテンは読み込み完了の HOLD_S 0.3秒後に開き、開く幅は easeInOut(p) × 半分の幅103 pxで、OPEN_S 1.1秒のあいだに最初の回で3.60秒から4.70秒まで進みます。左は clip でロゴを半分ずつに切って左右へ流し、本文は1.08倍から1倍へ沈みながら現れ、右は一枚が PH 150 pxぶん上がってロゴもまるごとついていきます。サイトの最初のアクセスで、ロゴの奥から本文を開くイントロに使います。

二枚の幕が割れて本文が現れる切り替え
新しいタブで開く

04 · 二枚の幕が割れて本文が現れる切り替え — 計測 累積面積 30.906% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

HALF_WHOLD_SOPEN_S
    function openP(t) { return clamp((t - (doneT + HOLD_S)) / OPEN_S, 0, 1); }
    // 片側のカーテンが押し出された距離 = 開きの曲線 × 半分の幅
    function HALF_W(t) { return easeInOut(openP(t)) * (PW / 2); }
    function drawCover(px, py, cx, f, shift, c, cl, cr) {
        // 左半分 — clip は半分の四角形。ロゴは中央に描くので半分だけ見える
        const half = PW / 2;
        ctx.save();
        ctx.beginPath();
        ctx.rect(px - shift, py, half, PH);
        ctx.clip();
        box(px - shift, py, half + 1, PH, 0, mix(c, FACE, cl), 1);
        ctx.translate(-shift, 0);
        logoWipe(cx, py + PH / 2 - 6, 64, f, 14, LAV_DEEP, 1);
        ctx.restore();
        ctx.save();
        ctx.beginPath();
        ctx.rect(px + half + shift, py, half, PH);
        ctx.clip();
        box(px + half + shift - 1, py, half + 1, PH, 0, mix(c, FACE, cr), 1);
        ctx.translate(shift, 0);
        logoWipe(cx, py + PH / 2 - 6, 64, f, 14, LAV_DEEP, 1);
        ctx.restore();
    }

05文字が順に湧き上がるバネのワードマーク

最初の文字は読み込み率が15%に届く時刻(最初の回で1.07秒)に出発し、次の文字は STAGGER_MS 90ずつ遅れます。減衰比0.465のばねは0.22秒で頂点1.192まで目標を19%行き過ぎ、510 ms後に2%以内で落ち着くので全体は960 msで、右は500 msかけて行き過ぎなく上がります。ブランド名を一文字ずつ登場させるワードマークに使います。

文字が順に湧き上がるバネのワードマーク
新しいタブで開く

05 · 文字が順に湧き上がるバネのワードマーク — 計測 累積面積 14.11% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

STAGGER_MSSPRING_KDAMPING_C
    function build() {
        makeLoad(SEED + (lap % 3), 6, 0.3, 2.5);
        t0 = timeAtFrac(START_FRAC);
        // ばねが目標の2%以内に入って留まる時刻 — 0.01秒間隔で後ろから探す
        settleS = 0;
        for (let q = 2.0; q > 0; q -= 0.01) {
            if (Math.abs(1 - springStep(q, SPRING_K, DAMPING_C)) > 0.02) {
                settleS = q;
                break;
            }
        }
    }
    function update(dt) { if (dt < 0) {
        return;
    } }
    function letterStart(i) { return t0 + (i * STAGGER_MS) / 1000; }
    // 文字 i の上がった度合い — ばねは1を一度超える
    function letterRise(i, t, spring) {
        const q = t - (spring ? letterStart(i) : t0);
        return spring ? springStep(q, SPRING_K, DAMPING_C) : ease(q / FLAT_S);
    }
    // 全体の長さ(ms) — 最後の文字の出発の遅れ + ばねが落ち着く時間
    function TOTAL_MS() { return (WORD.length - 1) * STAGGER_MS + Math.round(settleS * 1000); }

06ロゴの中心から広がる円形の開き

必要な半径は MAX_RADIUS() で、中心から四隅までの距離111.7・171.6・84.1・155.1 pxのうち最大の171.6 pxです。半径は ease(p) × 上限で EXIT_MS 900 msかけて伸び、中心が左下寄りなので距離が四つに分かれ、半径を FIXED_RADIUS() の103 pxに固定した右は三つの隅が覆われたまま残ります。ボタンやロゴの位置から画面を開く円形の切り替えに使います。

ロゴの中心から広がる円形の開き
新しいタブで開く

06 · ロゴの中心から広がる円形の開き — 計測 累積面積 26.758% · 動いたフレーム 6/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

EXIT_MSMAX_RADIUSFIXED_RADIUS
    function openP(t) { return clamp((t - OPEN_AT) / (EXIT_MS / 1000), 0, 1); }
    // 中心(fx, fy)から四角形の四隅までの距離 — 左上・右上・左下・右下
    function cornerDist(fx, fy) {
        const w = PW;
        const h = PH;
        return [Math.hypot(fx, fy), Math.hypot(w - fx, fy), Math.hypot(fx, h - fy), Math.hypot(w - fx, h - fy)];
    }
    function MAX_RADIUS() {
        const d = cornerDist(FX * PW, FY * PH);
        return Math.max(d[0], d[1], d[2], d[3]);
    }
    // 固定半径の失敗版 — 画面の幅の半分に決め打ちする
    function FIXED_RADIUS() { return PW / 2; }
    function radiusAt(t, full) { return ease(openP(t)) * (full ? MAX_RADIUS() : FIXED_RADIUS()); }

07資源の重みで分けたリングの区間

八つのリソースがリングをKBの比で分け、各弧は ARC_BY_KB(i, weighted) でそのリソースが読み込まれた分だけ満ちます。大きさは80・293・34・61・80・200・100・259 KB(合計1107 KB)で、最大の弧は86.8度、最小の弧は10.1度です。同じ角度に分けた右は中央の数字が弧の平均になり、2.90秒には実際との差が最大6.2ポイントに開きます。

資源の重みで分けたリングの区間
新しいタブで開く

07 · 資源の重みで分けたリングの区間 — 計測 累積面積 6.937% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

ARC_BY_KBSEG_GAPRING_W
    // 弧 i の開始角度と大きさ — weighted ならKBの比、そうでなければ同じ大きさ
    function ARC_BY_KB(i, weighted) {
        const room = TAU - SEG_GAP * RES_N;
        let a = -PI / 2;
        for (let k = 0; k < i; k += 1) {
            a += (weighted ? (res[k].kb / totalKb) * room : room / RES_N) + SEG_GAP;
        }
        return [a, weighted ? (res[i].kb / totalKb) * room : room / RES_N];
    }
    function meanFrac(t) {
        let s = 0;
        for (let i = 0; i < res.length; i += 1) {
            s += resFrac(res[i], t);
        }
        return s / res.length;
    }

08ロゴがヘッダーの位置へ飛ぶ引き継ぎ

読み込み完了(最初の回で2.50秒)の0.25秒後に大きなロゴが FLY_S 0.8秒かけて曲線の道を飛び、同じ進みの値で LOGO_BIG 70 pxから LOGO_SMALL 22 pxへ縮みます。本文は飛び始めの0.4秒後から0.6秒かけて、骨組みの表示から実際の内容へ入れ替わります(3.15秒から3.75秒)。右は大きなロゴが消えて350 ms後にヘッダーのロゴが現れるので、その間ロゴがありません。

ロゴがヘッダーの位置へ飛ぶ引き継ぎ
新しいタブで開く

08 · ロゴがヘッダーの位置へ飛ぶ引き継ぎ — 計測 累積面積 31.064% · 動いたフレーム 9/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

FLY_PATHSCALE_LERPXFADE
    function flyAt() { return doneT + 0.25; }
    function flyP(t) { return clamp((t - flyAt()) / FLY_S, 0, 1); }
    // 本文のクロスフェード — 飛び始めの XF_AT 後から XF_S のあいだ、骨組みが消えて内容が現れる
    function XFADE(t) { return ease((t - flyAt() - XF_AT) / XF_S); }
    // 始点・終点・制御点 — 制御点は始点の x と終点の y なので、先に上へ立ち上がってから左へ折れる
    function FLY_PATH(x0, y0, x1, y1, p) {
        const e = easeInOut(p);
        const cxp = x0;
        const cyp = y1;
        const a = (1 - e) * (1 - e);
        const b = 2 * (1 - e) * e;
        const c = e * e;
        return [a * x0 + b * cxp + c * x1, a * y0 + b * cyp + c * y1];
    }
    function SCALE_LERP(p) { return lerp(LOGO_BIG, LOGO_SMALL, easeInOut(p)); }

09ランディングのヒーローへ続くブランドイントロ

読み込み完了(最初の回で2.80秒)の0.25秒後に六枚のブラインドが BLIND_STAGGER 0.07秒ずつ遅れて、一枚あたり0.5秒かけて上がり、3.05秒から3.90秒で開き切ります。後ろのヒーローは3.55秒に始まり、二行の見出しが clip で湧き上がり、ボタンが減衰ばねで弾み、六つの玉が楕円の軌道を回ります。ランディングページの最初の画面のイントロに使います。

ランディングのヒーローへ続くブランドイントロ
新しいタブで開く

09 · ランディングのヒーローへ続くブランドイントロ — 計測 累積面積 11.057% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

BLIND_OPENBLIND_STAGGERSPRING_K
    function build() { makeLoad(SEED + (lap % 3), 8, LOAD_START, LOAD_SPAN); }
    function update(dt) { if (dt < 0) {
        return;
    } }
    function blindStart(i) { return doneT + 0.25 + i * BLIND_STAGGER; }
    // ブラインド i の開き — 行の高さがこの値だけ縮む
    function BLIND_OPEN(i, t) { return ease((t - blindStart(i)) / BLIND_S); }
    function heroT() { return blindStart(BLIND_N - 1) + 0.15; }

スプラッシュ画面のデザインをコードで書くと、ロゴひとつと数字ひとつはいくつの値で決まるのでしょうか?

進捗の値は仮想リソースの表ひとつから出ます

makeLoad(seed, n, start, spanS) は、種からn個のリソースの大きさ(24〜300 KB)と届く区間を決め、spanS 秒のうちに読み込みが終わるように合わせます。読み込み率は loadFrac(t) = 読んだKB ÷ 全体のKBで、01の最初の回は全体1103 KBを0.4秒から7.4秒までかけて受け取ります。種は回ごとに シード + lap % 3 と変わるので、三通りの読み込みが順番に現れます。02だけは makeLoad を呼ばず、固定の VISIT_LOAD(0.35秒と2.4秒)を使います。

    function makeLoad(seed, n, start, spanS) {
        const rnd = seeded(seed);
        const raw = [];
        let sum = 0;
        let mx = 0;
        for (let i = 0; i < n; i += 1) {
            const kb = Math.round(24 + rnd() * 276);
            const a = rnd() * 0.5;
            const b = a + 0.12 + (kb / 300) * 0.38;
            raw.push({ kb: kb, t0: a, t1: b });
            sum += kb;
            mx = Math.max(mx, b);
        }
        res = raw.map(function (r) { return { kb: r.kb, t0: start + (r.t0 / mx) * spanS, t1: start + (r.t1 / mx) * spanS }; });
        totalKb = sum;
        doneT = start + spanS;
    }
    // リソースひとつが時刻 t にどれだけ読み込まれたか、0から1

画面の演出はその値を受けて計算します

ロゴのマスク幅(01)・数字の目標(03)・文字の出発時刻(05)・リングの弧の角度(07)・ブラインドの開始(09)が、同じ表から枝分かれして出ます。実際のサイトでは loadFrac の位置に、読んだバイト数 ÷ 全体のバイト数を入れれば、残りのソースがそのまま使えます。デモはネットワークを呼ばず、乱数も種固定の生成器ひとつが作ります。

時計は sceneT ひとつです

Date・performance・Math.random の呼び出しは九つのソースに0件で、場面の時計が増えるのは sceneT += dt の一か所だけです。一コマは DT_MAX の0.05秒を超えないように切るので、タブが隠れて戻っても時間が一度に押し寄せません。同じ種の再生は何度回しても同じ値を描きます。

読み込み率と経過時間を受け取り、最短表示時間を守り、数字を追いかけさせ、マスク幅と開きの半径に変える — スプラッシュが一コマ計算される順番です

1コマの計算の順番

requestAnimationFrame が渡す時刻の差を DT_MAX で切り、advance(dt) が sceneT に足して、LAP_S を超えたら lap を上げて begin() で最初からやり直します。続けて update(dt) が項目の状態を直し、draw() がキャンバスを描き、状態行と進捗バーと完了の知らせが更新されます。最初の回は START_S ぶん先から始まるので、演出がすでに進んだ場面から見え始めます。

読み込み率が f に届く時刻を探す二分探索

読み込み率は時間とともに減らないので、05の timeAtFrac(f) は0から完了時刻までを40回半分に狭めて、はじめて f に届く時刻を見つけます。この一つの値で最初の文字の出発が決まり、次の文字は STAGGER_MS ずつ遅れてつながります。

    function timeAtFrac(f) {
        let lo = 0;
        let hi = doneT;
        for (let i = 0; i < 40; i += 1) {
            const mid = (lo + hi) / 2;
            if (loadFrac(mid) < f) {
                lo = mid;
            }
            else {
                hi = mid;
            }
        }
        return hi;
    }

スプラッシュ画面のアプリ風 UI を実際のサイトに付けるとき — ライブラリなしで作る方法・読み込みが速すぎても遅すぎても不自然な理由・本文へ渡す時点

最初の画面とウェブアプリの初期読み込み — どこに付けるか

モバイルのウェブアプリが最初の画面を受け取るあいだは、01のマスクのワイプと03のパーセント表示が合い、画像や書体やデータを別々に受け取る画面では、07のリングが資源の大きさぶんだけ弧を分けます。ブランドのランディングページには、09のブラインドが開いてヒーローにつながるイントロが似合い、ロゴがヘッダーへ上がる作りは08が合います。スプラッシュ画面 作り方は、どの画面でどの場面が必要かを先に選べば半分が決まります。

読み込みが速すぎても遅すぎても不自然な理由

読み込みが0.35秒で終わるとロゴは350 msだけ見えて消えるので、02のように最短表示時間を置きます。反対に読み込みが遅いと、03の右の盤のように数字が1.46秒のあいだ動かず止まって見えるので、目標を追う数字に変えて動きをつなぎます。スプラッシュ画面 デザインは、この二つの極端のあいだでロゴの留まる時間を決める作業です。

本文へ渡す時点

スプラッシュを終える瞬間は、読み込み完了の時刻に0.25〜0.3秒の余裕を足した値が基準です。04は HOLD_S の0.3秒、08と09は0.25秒を置いてから開くか飛び、06は OPEN_AT の3.2秒に開き始めます。ページ遷移全体の読み込み状態は ページ遷移とローディング状態 で別に扱っています。

ライブラリなしで作る

キャンバス2Dだけで描き、九つのソースにライブラリ・WebGL・CDNが0件なので、コピーしたファイルひとつで動きます。ロゴは丸いタイルに穴を二つ開けた図形で logoPath の一関数に入っており、別の図形に差し替えられます。ロゴの線がひとすじ描かれる側は ロゴのローディングアニメーション、スピナーとバーの側は CSSのローディング画面デザイン、CSSのオープニングは CSSのイントロ・オープニングアニメーション が隣にあります。

スプラッシュ画面が壊れるのは、読み込みが速くてロゴが一瞬で消える落とし穴・数字が飛んで止まったように見える進捗・固定半径の円形の開きが角を残すときです

読み込みが速くてロゴが一瞬で消える

02の速い訪問は読み込みが0.35秒で終わるため、完了した直後に出る盤は350 msで消えて、FLASH_MS の500 msを下回ります。最短表示時間を守る盤は600 msを満たしてロゴが読めます。同じコードの遅い訪問(2.4秒)では二つの盤とも2400 msで同じになり、この落とし穴は速い訪問だけに出ます。

数字が飛んで止まったように見える進捗

リソースがまるごと届いたときだけ上がる目標をそのまま出すと、03の右の盤のように270 KBの塊ひとつで25ポイントが一度に上がります。到着と到着のあいだは、いちばん長くて1.46秒(3.54秒から5.00秒)、同じ数字のまま立ち止まります。左の盤は CATCH_RATE 5で追うので、一回の到着の95%に0.60秒で届きます。

固定半径が角を残す円形の開き

06で半径を画面の幅の半分103 pxに決め打ちすると、開きが終わっても三つの角(111.7・171.6・155.1 px)が覆われたまま残ります。角が二つでなく三つなのは、中心が左下寄りで距離が四つに分かれ、103 pxを超えないのが84.1 pxのひとつだけだからです。直し方は MAX_RADIUS() で、いちばん遠い角までの距離171.6 pxを使うことです。

三通りの読み込みの種で回る九つの場面のHTMLとReact版のソースは下のzipにまとまっていて、展開するパスワードはdu9w8544です。

最短表示時間と文字の間隔と開く速さを変えると手ざわりはどれだけ変わるのでしょうか — MIN_HOLD・STAGGER_MS・EXIT_MS を変えて比べます

名前 変えた値 手ざわり
最短表示時間を延ばす 02 MIN_HOLD 0.6 → 1.4 速い訪問では左の退場開始が600 msから1400 msへ遅れてロゴが800 ms長く留まり、右の350 msのちらつきはそのままです。読み込みに2.4秒かかる遅い訪問はどちらも2400 msで、値を変えても変わりません。
文字の間隔を狭める 05 STAGGER_MS 90 → 30 全体の長さは960 msから660 msへ縮み(最後の文字の出発は450 msから150 ms)ますが、ばねが落ち着くまでの510 msは変わらないので、文字がほぼ重なって塊のように湧き上がります。
開きをもっと速く 06 EXIT_MS 900 → 450 半径が171.6 pxまで伸びる平均の速さは191 px/sから381 px/sへ(最高速度は286から572 px/sへ)二倍になり、開きは短くなりますが、本文が現れる瞬間を目で追いにくくなります。

値を変える前に見ること

この三つの値は02・05・06それぞれの定数なので、一つの場面で値をひとつだけ変えると比べやすくなります。02は速い訪問の600 msと350 msを、05は最初の回の960 msを、06は最初の回の171.6 pxを基準に見てください。lap が変わると種も変わって読み込みの時間表も変わるので、上の数値と合わせるには最初の回で比べます。

動きを減らす設定では止まった一枚のロゴ画面です — 場面の名前といまの進捗を文で読み上げるアクセシビリティです

Enter・Space・R でやり直し、P・N で操作する

ステージの箱は九つの場面とも role="application" で、Enter・Space・R・クリックが最初からやり直し、Pが一時停止、Nが一歩です。九つの場面の時間表が空なので、Nも最初からやり直す動作になります。デモは操作しなくても最後まで自動で進みます。

進捗バーと状態行と完了の知らせ

進捗の値は role="progressbar" の要素の aria-valuenow で0から100まで読まれ、値が変わったときだけ書き換わります。状態行 data-role="status" は再生中 aria-live="off" なので毎フレームは読み上げられず、人がはじめて触ると polite に変わります。完了の知らせは data-role="done"(role="status")に RESULT_S 秒で一度だけ書きます。

動きを減らす設定と隠れたタブ

prefers-reduced-motion が有効だとループを回さず、REST_S 秒の止まった一枚を showRest() で描き、入力してもすぐその一枚を描き直します。02・04・06・08・09は演出が終わった本文の画面で、01・03・05・07は読み込みが終わって値がすべて満ちた画面です。九つの場面とも visibilitychange でタブが隠れるとループを止め、戻ると続きから動かします。

スプラッシュ画面 FAQ — ライブラリなしで作る方法・読み込みが速すぎるときの対処・ウェブに載せるときに必要なこと

スプラッシュ画面 デザインはライブラリなしで作れますか

作れます。キャンバス2Dと requestAnimationFrame だけで九つの場面が動き、ソースにライブラリ・WebGL・CDNは0件です。動きの仕組みは MDNのrequestAnimationFrame と、半径で切り抜く clip-path のドキュメントが説明しています。

読み込みが速すぎるときはスプラッシュをどうしますか

02のように最短表示時間を置きます。退場の開始を読み込み完了と MIN_HOLD の大きいほうにすると、速い訪問でもロゴが600 ms留まり、遅い訪問は待った2400 msのままです。値を1.4秒に上げると速い訪問は1400 msまで延び、遅い訪問は変わりません。

スプラッシュ画面 アプリに載せるとき必要なことは何ですか

実際に読んだバイト数と全体のバイト数から loadFrac の位置の値を作り、読み込みが終わる時点を知らせる一行をつなぐだけです。動きを減らす設定とタブを隠す処理はソースに入っているのでそのまま残し、タブが隠れる動作は Page Visibility API、動きの設定は prefers-reduced-motion のドキュメントが説明しています。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。