WebGL で動くギャラリー:慣性と余韻のある、曲線状のスライド

IXcode®

01/07

Drag or scroll

← ブログ一覧へ

WebGL で動くギャラリー:慣性と余韻のある、曲線状のスライド

要点

  • 位置を「目標」と「いま」に分け、残りの距離の一定割合だけ近づけることで余韻を作る
  • 動きの速さを、たわみ・色ずれとして見た目に返す
  • 止まったら、いちばん近いカードへ吸い付かせる

仕組み

  1. 目標といま:ホイールやドラッグは、目標の位置を動かします。いまの位置は、毎フレーム、目標へ近づきます。始めは速く、近づくほどゆっくりです。
  2. 離したあとに勢いを足す:ドラッグの最後の速さを、目標に足します。弾いた分だけ、遠くまで滑ります。
  3. 吸い付く:入力が 0.17 秒止まったら、目標を、いちばん近いカードの位置へ寄せます。ぴたっと止めず、ゆっくり落ち着かせます。
  4. 速さを見た目に返す:位置の変化の速さを、シェーダーに渡します。速いほど、カードがたわみ、赤と青が左右にずれます。
  5. カメラも遅れて追う:マウスの位置と動きの勢いに合わせて、カメラをゆっくり動かします。

コード

// 毎フレーム:時間に左右されない、なめらかな近づき方
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)と英字フォントを、外部から読み込んでいます。