本文へスキップ

作品サイト|(つづく)

2023.07
クリエーティブ・ディレクター・コレクティブ(つづく)の作品サイトのデザインと実装を担当。トップのサムネイル一覧を WebGL で描いている

クリエーティブ・ディレクター・コレクティブ「(つづく)」の作品サイト。デザインと実装の両方を担当した。

トップページには作品のサムネイルが縦に並び、スクロールすると終わりなく続いていく。この一覧はすべて WebGL(react-three-fiber)で描いていて、画面の上下に向かってほんの少しだけ円弧を描くように曲がっている。また、スクロールに合わせてサムネイルの中の画像が枠の中でわずかにずれ、パララックスのような奥行きが出るようにした。

作品の情報は microCMS で管理し、Next.js で静的に書き出して Vercel にデプロイしている。日本語と英語の2言語。

円弧を描くサムネイル

曲げているのは頂点シェーダー。画面中央からの縦の距離 y に対して、頂点の奥行き z を円の式で決めている。

float distanceFromCenter = abs(modelMatrix * vec4(position, 1.)).y;
newposition.z = 0.2 - sqrt( 40. - pow(distanceFromCenter, 2.) ) * .07 * (2. - uSpeed);

sqrt(40 - y²) は半径 √40 の円なので、画面の中央にある頂点ほど奥に、上下の端にある頂点ほど手前に来る。ドラムの内側を覗き込んでいるような形になり、透視投影によって上下端のサムネイルが少しだけ外側に広がって見える。

ポイントは、サムネイル自身の座標ではなくワールド座標の y で曲げていること。曲面は画面に固定されていて、サムネイルはその上を流れていく。そのため同じサムネイルでも、画面の中央ではほぼ平らに、上下の端に近づくほど手前に反って見える。

キャプションの文字(drei の Text)にも同じ頂点シェーダーを当てていて、文字もサムネイルと一緒に曲がる。

スクロールに合わせたパララックス

フラグメントシェーダーで、テクスチャを読む UV を画面上の縦位置に応じてずらしている。

vec2 uv = vUv * s / new * .97 + offset;

vec2 yOffset = vec2(0., vWorldPos.y);
vec4 tex = texture2D(map, uv + yOffset.xy * 0.01);

サムネイルが画面の下から上へ通り過ぎる間に、中の画像は枠に対して数%だけ逆方向に動く。枠と中身が別の速さで動くので、スクロールに奥行きが出る。

UV に掛けている .97 は、ずらしたときに画像の外側が見えないよう、あらかじめ少し拡大しておくためのもの。

無限スクロール

DOM のスクロールは使っていない。virtual-scroll でホイールとタッチの入力を正規化して目標位置だけを更新し、毎フレーム lerp(現在位置, 目標位置, 0.08) で追いかけることで慣性のある動きにしている。

ループの仕組みは、列ごとに 150ms おきに frustum との当たり判定をとり、画面の外に出たサムネイルをその列の全長ぶんだけ反対側に移動させるというもの。上に抜けたものは下へ、下に抜けたものは上へ回り込むので、どちらにスクロールしても終わりがない。

作品は canvas の幅が 640px 以下なら1列、768px 以下なら2列、それより広ければ3列に振り分ける。サムネイルの縦横比が作品ごとに違うため、列ごとに全長が異なり、ループの周期も列ごとにずれる。同じ組み合わせが並び直すことがほとんど無く、いつまでスクロールしても一覧が単調にならない。

細かいところ

  • 戻ってきたときのスクロール位置。 ページ遷移の直前に位置を保存しておき、一覧に戻ったときにその位置から再開する。無限に続く一覧で毎回先頭に戻されると、どこまで見たか分からなくなるため
  • テクスチャの遅延読み込み。 各列の最初の6枚だけを先に読み、残りは画面の2画面分以内に近づいてから読み込む。動画のサムネイルは VideoTexture で再生し、読み込むまではポスター画像を表示する
  • 色味。 react-three-fiber は既定でトーンマッピングが掛かり、写真の色がくすむ。作品のサムネイルは色が命なので、画像と動画のマテリアルではトーンマッピングを切っている
  • クローラー向けの DOM。 一覧が canvas の中にしか無いと検索エンジンからリンクが辿れないので、同じ作品一覧を不可視のリンクリストとしても出力している

構成

  • Next.js(Pages Router)/ TypeScript / next-i18next
  • react-three-fiber / drei / framer-motion-3d / virtual-scroll
  • jotai / Tailwind CSS
  • microCMS
  • Vercel