このサイトの背景には、ページを開くたびに生成される星空があります。画像でもCSSアニメーションのループ素材でもなく、<canvas> すら使わず、<div> を並べているだけです。派手な演出に見えないよう、乱数の効かせ方をかなり細かく調整しました。

星の数は「画面幅 ÷ 9」

星の数を固定値にすると、スマホでは窮屈に、ワイドモニターではスカスカになります。そこで画面幅から逆算する式にしました。

const count = Math.min(160, Math.round(window.innerWidth / 9));

上限を160に設定しているのは、超ワイドモニターで星が増えすぎて背景が白飛びするのを防ぐためです。密度の目安(9px間隔)は、実際にモバイル〜デスクトップの複数解像度を目視しながら決めた数字で、理屈より「見た目が気持ちいいか」を優先しました。

サイズと色は確率で決める

すべての星を同じ見た目にすると、規則的すぎて人工的な星空に見えます。そこで大きさと色に軽いばらつきを持たせています。

const size = Math.random() < 0.82 ? 2 : 3;
s.style.width = size + "px";
s.style.height = size + "px";
s.style.left = Math.random() * 100 + "%";
s.style.top = Math.random() * 78 + "%";

if (Math.random() < 0.2) {
  s.style.background = "var(--accent)";
  s.style.boxShadow = "0 0 6px var(--glow)";
}

ポイントは配分です。82%は小さな白い点、18%だけ少し大きく、さらにその中の20%だけがアクセントカラーで光る——という二重の確率にすることで、「たまに目を引く星がある」状態を作っています。全部を均一に光らせると賑やかすぎて逆に安っぽく見えるので、「主役は少数」というバランスを数値で作った形です。

縦方向を Math.random() * 78 + "%" に制限しているのも意図的で、下部20%強は他のセクションと重なる領域なので、星を置かないようにしています。

またたきはCSSアニメーションに丸投げ

またたき(明滅)はJSでは一切制御していません。CSS側で一つの @keyframes を用意し、JS側は --dur(周期)と animation-delay(開始タイミングのずらし)だけを変数で渡しています。

s.style.setProperty("--dur", 2.5 + Math.random() * 4 + "s");
s.style.animationDelay = -Math.random() * 5 + "s";

animation-delay に負の値を使っているのがミソです。負の遅延は「アニメーションがすでにその時間分進んだ状態から開始する」という意味になるので、ページを開いた瞬間からすべての星がバラバラの明滅フェーズにいる状態を作れます。もし正の遅延だけを使うと、全ての星が「暗い状態」からスタートしてしまい、ロード直後に一瞬”点灯”が揃って見えてしまいます。

生成は一度きり、DOMは触らない

星空は初回マウント時に DocumentFragment へまとめて追加し、以降は一切再生成しません。モード切替(昼夜のテーマ切替)のたびに星を消して作り直すと、その瞬間だけ明滅のリズムがリセットされて不自然な”瞬き揃い”が起きます。実際には星の透明度をCSS変数で下げるだけにして、DOM自体は最初の一回で完成させています。

乱数を使う演出は「盛れば盛るほど良い」わけではなく、どこにばらつきを持たせ、どこを均一に保つかの線引きが効いてくる、という実装でした。