24
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クソアプリAdvent Calendar 2024

Day 12

🌟星占い10万年でブラウザの限界を越える🌟

Last updated at Posted at 2024-12-12

ご無沙汰してます。ここはクソアプリアドベントカレンダーの12日目です。
今年も無事クソアプリを生み出すことができました。
皆さんの来年がより良いものになるよう、今日は10万年分の星占いをお送りします:cat:

過去ログ

できたもの

アプリ:
https://yuneco.github.io/perpetual-astrology/

ソースコード:
https://github.com/yuneco/perpetual-astrology

:sparkles: 解説と見どころ :sparkles:

名前と星座を入れると…

秒で10万年分、毎日の運勢を占って勿体ぶらずに全部表示します

好きなところまでスクロールしてタップしたら…
scrollandtweet.gif

X(Twitter)でシェアしましょう

リンクがつくので10万年分の運勢を全部インターネッツに大公開できます:blush:

:sparkles: 使った技術 :sparkles:

今回は稀に見るシンプルさです→package.json

ざっくりいうと、

  • React 19
  • Kuma UI

だけですね。環境周りは普通にVite + TSです。

:sparkles: よくわかる解説 :sparkles:

もう大体の人はわかってると思うけど、やってることはただの仮想スクロールです。
10万年=36500000日分のDOMを律儀に作ると当然まともに動かないので、画面に見えている範囲だけDOMを作ろうってやつです。

ということで10万年星占いを作るのに必要なのは、

  • 36500000行の巨大リストを仮想スクロールする実装
  • スクロール位置に合わせた星占いを表示する実装

の2つだけ、ということになります。簡単ですね:wink:

ブラウザのスクロールは星占いには狭すぎる

そう、仮想スクロール自体は難しくないんですよ1
数年前に流行って散々擦られたので良くできたライブラリも多そうです(こういうのとか:react-infinite-scroller

ただし、これはリストのサイズが常識的な範囲の場合です。実は現代の主要ブラウザが扱えるDOMのスクロール上限は:star2:星の世界:star2:から見ると結構小さいんです。

詳しいことはバーチャルスクロールの限界を突破するを参照いただくとして、
Chromeの限界は16777216px。1行(1日分)の高さが50pxだとすると、これって大体919年分。たった一千年の星占いすら表示できない有様です:poop:

ちなみに10万年分に必要な高さは18億2500万pxです

限界を越える戦略

前出の解説記事ではスクロールバーとスクロール機能を自作していましたが、今回我々が作るのはクソアプリです。そんなクソめんどくさいことやりたくないですよね?

ちょっと真面目な話としては、きちんと動作するスクロールの実装のためには

  • マウス・ホイール・タッチ等の各種入力の対応
  • スペース・PageUp等、各種キー操作の対応
  • バーを直接ドラッグした場合や、トラック部分をクリックした場合の対応
  • ネイティブと遜色ない見た目と性能
  • アクセシビリティのケア

…などなど考えないといけないことがものすごく多いです。相応の覚悟がない限りやるべきではありません。今回は手抜きのため、できる限り標準のスクロールを使って限界を超えます

というわけで、下図は10万年星占いのDOMをちょっとみやすく調整したものです。

image.png

実は画面右に出ていたスクロールバーは本体のリストとは別物です。この細いスクロールエリア(赤)を「ミニマップ」とでも呼びましょう。
メインのスクロールエリア(青)は画面全体に配置した上でスクロールバーを隠します。こっちを「メインエリア」と呼びましょう。
肝心の星占いのコンテンツ(緑)は2つのスクロールとは別にposition: stickyで画面上に固定します。

まず、ミニマップ(赤)からです。
ミニマップは実際には画面の10倍程度の高さしかない普通のスクロールバーです。現在位置を示したり、直接バーをドラッグしたりして10万年をスクロールできます。わかりやすく高さを1800pxとすると、このスクロールバーの値x100000が実際のコンテンツの仮想スクロール位置になります。
minimap.gif

次にメインのスクロールエリア(青)です。
こっちはもう少し高さを確保していますが、それでも20000px程度のスクロールバーです。初期位置は真ん中になっていて、上下どちらにでもスクロールできます。
ただ、これだけだと上下に10000pxしかスクロールできません。
これを誤魔化すために、隙を見てスクロールをリセットしています。
mainlist.gif

GIFをよくみていると、スクロールの合間合間でスクロールボックスの位置が中央にリセットされていることがわかると思います。リセットした際にスクロールされていた量はミニマップ(赤)のスクロール量に加算します。

最後のコンテンツエリア(緑)は特に何もありません。
この領域はスクロールせず、2つのスクロールバーから算出した仮想スクロール量に応じて日付と占い内容を書き換えます。

完璧とは言い難いですが、標準のスクロールUIを使いながらでもブラウザの限界を突破できましたね :tada:

10万年分の星占いを計算する

もうここまでで面白いことは大体書き切っちゃったのですが、一応星占いアプリなんでコンテンツの方の話もしましょう。いうまでもなくこの占いはただの乱数なのですが、JavaScript標準のMath.random()を使うわけにはいきません。

同じ名前・星座を入れているのに同じ日の運勢が毎回違ったり、スクロールして戻ってきたら運勢が変わっている、なんてことになったら興醒めです。

運勢 = f(名前, 星座, 日付)

のような純粋な関数が必要です。
まあ、要は乱数ではなくハッシュですね。

ブラウザ標準で使えるハッシュ関数としてはSubtleCrypto: digest()のような便利なものもあるのですが、クソアプリで使うには少々真面目すぎるのと、インターフェースが非同期関数で面倒なのでもっと手抜きします。

今回はChatGPTにメルセンヌ・ツイスターの擬似乱数列生成器を書いてもらいました
多少の効率化はしていますが、基本は名前, 星座, 日付を元に作ったシード値をメルセンヌ・ツイスターにぶちこんで疑似乱数を生成して使い捨てています。
メルセンヌ・ツイスターの無駄遣い。

ハッシュ値が得られればあとはごにょごにょ加工して⭐️の数にしたり、ラッキーカラーにしたり、サイゼリアのメニューにしたりラッキーUUIDにしたりやりたい放題です。

:sparkles: 技術的な感想 :sparkles:

React 19

:bear: Kuma UI

  • 仕事で使うには時期尚早だと思ってあんまり触らずに放置してしまってたので、今回ノープラン&雰囲気で使ってみた
  • emotionの後継だって考えるのをやめて、KumaはKumaとして仲良くなろうと思ったら結構楽しくていい感じ
  • ただ、いかんせん未対応のプロパティや機能が多いので、謎の縛りゲー感が強い
    • @keyframeがKumaの機能では定義できない
    • transformの個別変形プロパティ(transform: scale(2)ではなくscale: 2みたいに書ける方)が使えない
    • ベンダープレフィックスどうすればいいんだろ?
    • ブレークポイント以外のメディアクエリって使えない、よね?
  • なのでやっぱり仕事で使うにはしんどいけど、個人開発で触る分には楽しいから期待してる

来年もよろしくお願いします!

今年クソアプリくらいしか作ってなくてお恥ずかしい限りですが来年もよろしくお願いします:wave:

  1. サーバ負荷含めた実運用とか快適なUX設計とかが簡単とは言ってないので注意

24
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?