ご無沙汰してます。ここはクソアプリアドベントカレンダーの12日目です。
今年も無事クソアプリを生み出すことができました。
皆さんの来年がより良いものになるよう、今日は10万年分の星占いをお送りします
過去ログ
- 2024 星占い10万年 👈いまここ
- 2023 🎉🎉最高にイカしたバウンディングボックスを紹介するぜ🎉🎉
- 2022 【ChatGPTと作る】あしのさきの動物パンプリン占い【クソアプリ】
- 2021 😡webのスクロールでふわっと出てくるやつ絶対粉砕するマン【クソアプリ】
- 2020 Vue.jsと物理演算とElectronで仕事中にデスクトップでお寿司をつまめるようになったのでソースと解説【クソアプリ】
できたもの
アプリ:
https://yuneco.github.io/perpetual-astrology/
ソースコード:
https://github.com/yuneco/perpetual-astrology
解説と見どころ
秒で10万年分、毎日の運勢を占って勿体ぶらずに全部表示します
X(Twitter)でシェアしましょう
できた。我ながら良いクソアプリだ
— ゆき (@yuneco) December 11, 2024
nekochanさん♈️牡羊座
99999年12月29日(水)の運勢
🔮運勢:★★★★
🔮ラッキーカラー:#fd3289
🔮ラッキーサイゼ:🧂エクストラ・バージン・オリーブオイル (500ml) + 🥔カリッとポテト + 🍚ラージライス (1680円) https://t.co/OsZj2XuvfN
リンクがつくので10万年分の運勢を全部インターネッツに大公開できます
使った技術
今回は稀に見るシンプルさです→package.json
ざっくりいうと、
- React 19
- Kuma UI
だけですね。環境周りは普通にVite + TSです。
よくわかる解説
もう大体の人はわかってると思うけど、やってることはただの仮想スクロールです。
10万年=36500000日分のDOMを律儀に作ると当然まともに動かないので、画面に見えている範囲だけDOMを作ろうってやつです。
ということで10万年星占いを作るのに必要なのは、
- 36500000行の巨大リストを仮想スクロールする実装
- スクロール位置に合わせた星占いを表示する実装
の2つだけ、ということになります。簡単ですね
ブラウザのスクロールは星占いには狭すぎる
そう、仮想スクロール自体は難しくないんですよ1
数年前に流行って散々擦られたので良くできたライブラリも多そうです(こういうのとか:react-infinite-scroller)
ただし、これはリストのサイズが常識的な範囲の場合です。実は現代の主要ブラウザが扱えるDOMのスクロール上限は星の世界から見ると結構小さいんです。
詳しいことはバーチャルスクロールの限界を突破するを参照いただくとして、
Chromeの限界は16777216px
。1行(1日分)の高さが50pxだとすると、これって大体919年分。たった一千年の星占いすら表示できない有様です
ちなみに10万年分に必要な高さは18億2500万px
です
限界を越える戦略
前出の解説記事ではスクロールバーとスクロール機能を自作していましたが、今回我々が作るのはクソアプリです。そんなクソめんどくさいことやりたくないですよね?
ちょっと真面目な話としては、きちんと動作するスクロールの実装のためには
- マウス・ホイール・タッチ等の各種入力の対応
- スペース・PageUp等、各種キー操作の対応
- バーを直接ドラッグした場合や、トラック部分をクリックした場合の対応
- ネイティブと遜色ない見た目と性能
- アクセシビリティのケア
…などなど考えないといけないことがものすごく多いです。相応の覚悟がない限りやるべきではありません。今回は手抜きのため、できる限り標準のスクロールを使って限界を超えます
というわけで、下図は10万年星占いのDOMをちょっとみやすく調整したものです。
実は画面右に出ていたスクロールバーは本体のリストとは別物です。この細いスクロールエリア(赤)を「ミニマップ」とでも呼びましょう。
メインのスクロールエリア(青)は画面全体に配置した上でスクロールバーを隠します。こっちを「メインエリア」と呼びましょう。
肝心の星占いのコンテンツ(緑)は2つのスクロールとは別にposition: sticky
で画面上に固定します。
まず、ミニマップ(赤)からです。
ミニマップは実際には画面の10倍程度の高さしかない普通のスクロールバーです。現在位置を示したり、直接バーをドラッグしたりして10万年をスクロールできます。わかりやすく高さを1800pxとすると、このスクロールバーの値x100000が実際のコンテンツの仮想スクロール位置になります。
次にメインのスクロールエリア(青)です。
こっちはもう少し高さを確保していますが、それでも20000px程度のスクロールバーです。初期位置は真ん中になっていて、上下どちらにでもスクロールできます。
ただ、これだけだと上下に10000pxしかスクロールできません。
これを誤魔化すために、隙を見てスクロールをリセットしています。
GIFをよくみていると、スクロールの合間合間でスクロールボックスの位置が中央にリセットされていることがわかると思います。リセットした際にスクロールされていた量はミニマップ(赤)のスクロール量に加算します。
最後のコンテンツエリア(緑)は特に何もありません。
この領域はスクロールせず、2つのスクロールバーから算出した仮想スクロール量に応じて日付と占い内容を書き換えます。
完璧とは言い難いですが、標準のスクロールUIを使いながらでもブラウザの限界を突破できましたね
10万年分の星占いを計算する
もうここまでで面白いことは大体書き切っちゃったのですが、一応星占いアプリなんでコンテンツの方の話もしましょう。いうまでもなくこの占いはただの乱数なのですが、JavaScript標準のMath.random()
を使うわけにはいきません。
同じ名前・星座を入れているのに同じ日の運勢が毎回違ったり、スクロールして戻ってきたら運勢が変わっている、なんてことになったら興醒めです。
運勢 = f(名前, 星座, 日付)
のような純粋な関数が必要です。
まあ、要は乱数ではなくハッシュですね。
ブラウザ標準で使えるハッシュ関数としてはSubtleCrypto: digest()のような便利なものもあるのですが、クソアプリで使うには少々真面目すぎるのと、インターフェースが非同期関数で面倒なのでもっと手抜きします。
今回はChatGPTにメルセンヌ・ツイスターの擬似乱数列生成器を書いてもらいました。
多少の効率化はしていますが、基本は名前, 星座, 日付
を元に作ったシード値をメルセンヌ・ツイスターにぶちこんで疑似乱数を生成して使い捨てています。
メルセンヌ・ツイスターの無駄遣い。
ハッシュ値が得られればあとはごにょごにょ加工して⭐️の数にしたり、ラッキーカラーにしたり、サイゼリアのメニューにしたり、ラッキーUUIDにしたりやりたい放題です。
技術的な感想
React 19
- 正直あんまりわかってないけど普通に使えた。別にReact 18がわかっていたわけではないのでそういう意味では変わらず。ありがたいことです
-
ref
にクリーンアップがついたので使ってみた。個人的にはReactのイベントハンドラ周りの扱いに辟易していたのでここら辺のスクロールイベントの処理を(フックじゃない)ただの関数に書いて、refでDOMに紐づける書き方を試してみた
Kuma UI
- 仕事で使うには時期尚早だと思ってあんまり触らずに放置してしまってたので、今回ノープラン&雰囲気で使ってみた
- emotionの後継だって考えるのをやめて、KumaはKumaとして仲良くなろうと思ったら結構楽しくていい感じ
- ただ、いかんせん未対応のプロパティや機能が多いので、謎の縛りゲー感が強い
-
@keyframe
がKumaの機能では定義できない -
transform
の個別変形プロパティ(transform: scale(2)
ではなくscale: 2
みたいに書ける方)が使えない - ベンダープレフィックスどうすればいいんだろ?
- ブレークポイント以外のメディアクエリって使えない、よね?
-
- なのでやっぱり仕事で使うにはしんどいけど、個人開発で触る分には楽しいから期待してる
来年もよろしくお願いします!
今年クソアプリくらいしか作ってなくてお恥ずかしい限りですが来年もよろしくお願いします
-
サーバ負荷含めた実運用とか快適なUX設計とかが簡単とは言ってないので注意 ↩