WebGL で動くギャラリー:慣性と余韻のある、曲線状のスライド
要点
- 位置を「目標」と「いま」に分け、残りの距離の一定割合だけ近づけることで余韻を作る
- 動きの速さを、たわみ・色ずれとして見た目に返す
- 止まったら、いちばん近いカードへ吸い付かせる
仕組み
- 目標といま:ホイールやドラッグは、目標の位置を動かします。いまの位置は、毎フレーム、目標へ近づきます。始めは速く、近づくほどゆっくりです。
- 離したあとに勢いを足す:ドラッグの最後の速さを、目標に足します。弾いた分だけ、遠くまで滑ります。
- 吸い付く:入力が 0.17 秒止まったら、目標を、いちばん近いカードの位置へ寄せます。ぴたっと止めず、ゆっくり落ち着かせます。
- 速さを見た目に返す:位置の変化の速さを、シェーダーに渡します。速いほど、カードがたわみ、赤と青が左右にずれます。
- カメラも遅れて追う:マウスの位置と動きの勢いに合わせて、カメラをゆっくり動かします。
コード
// 毎フレーム:時間に左右されない、なめらかな近づき方
var k = 1 - Math.exp(-dt * 4.4); // dt が変わっても、同じ速さで近づく
pos += (target - pos) * k; // いまの位置は、目標に向かって、ゆっくり近づく
// 速度(1秒あたりに進んだカードの数)を、さらになめらかにして、シェーダーへ
var v = (pos - prev) / dt;
svel += (v - svel) * (1 - Math.exp(-dt * 10));
material.uniforms.uVel.value = svel * 0.55;
// 入力が止まったら、いちばん近いカードへ吸い付かせる
if (now - lastInput > 170) {
var snap = Math.round(target);
target += (snap - target) * (1 - Math.exp(-dt * 5));
}
// 頂点シェーダー:速さに応じて、カードをたわませる
p.z += sin(uv.x * 3.14159) * uVel * 0.55;
// フラグメントシェーダー:速さに応じて、赤と青を左右にずらす
float s = clamp(uVel * 0.011, -0.035, 0.035);
col.r = texture2D(uTex, vUv + vec2(s, 0.0)).r;
col.b = texture2D(uTex, vUv - vec2(s, 0.0)).b;
写真の扱い
- カードの比率(400:316)に切り抜いて、縮小します。元は合計 14MB。PC 用(1200px 幅)とスマホ用(720px 幅)の 2 種類にしました。
- WebP に変換します。PC は 7 枚で約 460KB です。幅 768px 未満は、スマホ用を読み込みます。
- 届くまでは、canvas で描いた絵を表示します。読み込みに失敗しても、絵のまま使えます。
こだわり・気をつけたこと
- 近づく割合を、時間の長さ(dt)から計算します。画面の更新が遅い環境でも、動きの速さが変わりません。
- WebGL が使えない環境では、題名と説明を並べた一覧に切り替えます。
- 動きを減らす設定には、余韻とたわみを省きます。操作した位置へ、すぐ動きます。
- キーボードでも動かせます。← → ↑ ↓、PageUp・PageDown、Home・End、画面下の矢印ボタンに対応しています。
- 解説を開いている間は、ギャラリーを動かしません。
- 画面サイズが変わったら、カードの大きさと位置を計算し直します。スマホでは、カードを真ん中に置き、番号を上に移します。
写真は Unsplash の無料の写真です(Amanda Marie、Dhruv、Juliana Tanchak、Martin Martz、Naksha Banwao、Todd Trapani、Waro Photos。撮影者の皆さまに、感謝いたします)。three.js(r158)と英字フォントを、外部から読み込んでいます。