光るボタンは好き。でも、ボタンも背景もカードも全部光っていると、どこを見ればいいのか分からなくなる。

そこで「ボタンを明るくしたぶん、周りが暗くなったらどうだろう」と考えて、作ってみました。名前は Charge the Room。長押しで周囲のネオンから光を集めて、離すと放電します。

満タンになると、ボタンだけ景気がいい。周りの看板はほぼ休業です。

Yabi Labsに新しく作ったUI Labの、最初の実験作品です。

まずは押してみる

中央の丸いボタンを3秒ほど長押しして、離してみてください。「動きを見る」でも1回だけ再生できます。

大きな画面で遊ぶ / ソースをダウンロード

キーボードなら、Tabで中央のボタンに移動してSpaceを長押し。Enterは自動再生です。音は出ません。仕事中でも、周りの電気までは消えません。

光を増やすより、移動させる

仕組みの中心にあるのは、0〜1の値ひとつです。

let charge = 0;

chargeが0なら、周りの看板が明るい状態。1に近づくほど中央に光が集まり、周囲は暗くなります。画面下のROOMとCOREも、足すと常に100になるようにしました。

JavaScriptから渡すCSS変数は、こんな形です。実際の描画処理から抜き出しています。

root.style.setProperty("--charge", charge.toFixed(4));
root.style.setProperty("--ambient", (1 - charge).toFixed(4));
root.style.setProperty("--degrees", `${charge * 360}deg`);

ボタンの光量は--charge、周りの明るさは--ambient、円周のゲージは--degreesを見ています。別々にアニメーションを始めるより、ひとつの値を共有するほうが「光を持っていかれた」感じを合わせやすい。

ただし、これは物理シミュレーションではありません。画面上の電力メーターと明るさを連動させる、見た目のルールです。

真っ暗にはしない

周りの看板を完全に消すと、何が暗くなったのかも分かりにくい。そこで、最低限の輪郭は残しています。

.supply {
  opacity: calc(.17 + var(--ambient) * .83);
}

満タンでも17%は残る計算。中央だけ明るい状態と、暗い看板の存在を両方見せたかったので、この加減にしました。

中央の円周はconic-gradientで塗り、真ん中をマスクしてリングにしています。発光はCSS、放電時に飛ぶ粒子と広がる輪はCanvasの担当です。

長押しは「離した場所」も考える

押し始めはpointerdown、離すときはpointerupで受け取ります。ここで入れたのがsetPointerCapture()です。

button.addEventListener("pointerdown", event => {
  if (!event.isPrimary || event.button !== 0) return;
  if (start(event.pointerId)) {
    button.setPointerCapture(event.pointerId);
    button.focus({ preventScroll: true });
  }
});

指やマウスがボタンの外に移動しても、同じポインターのイベントをボタンで受け取れるようにします。押す場所は中央でも、離す場所まで中央とは限らないので。MDNの説明

操作が中断されたpointercancel、ウィンドウからフォーカスが外れたとき、タブが非表示になったときはリセット。戻ってきたら勝手に充電が終わっていた、という状態は避けています。

3秒は、フレーム数で数えない

蓄電はrequestAnimationFrameで更新します。1フレームごとに固定の量を足すと、画面の更新頻度によって満タンまでの時間が変わってしまうので、前回からの経過時間を使いました。

const dt = Math.min((now - previous) / 1000, .05);
charge = Math.min(1, charge + dt / 3);

通常の描画間隔なら約3秒で満タンです。長く処理が止まったときは、1回の更新を最大0.05秒ぶんに制限しています。そのため、極端に重い環境では3秒より長くかかります。requestAnimationFrameの時間の扱い

放電後、粒子が消えたら更新ループも止めます。何も操作していない間まで、ずっと描き続ける必要はありません。

派手にしたいけど、動きを減らす入口も置く

「動きを控えめに」を入れると、粒子と拡大する光を止めます。蓄電量、周囲の明るさ、操作結果の文章は残すので、何が起きたかは分かる状態です。

最初の設定にはOS側のprefers-reduced-motionを使っています。派手な版を作ることと、控えめに見られることは両立させたい。MDNの設定例

自分のサイトで使うなら

ソース一式をダウンロードできます。HTML・CSS・JavaScriptだけで、ライブラリやビルドは不要。展開してindex.htmlを開けば動きます。

色はCSSの--accent、蓄電時間はJSのdt / 3で調整できます。時間を変えるときは、自動再生側の待ち時間も合わせて変更してください。詳しくは同梱のREADMEに書いています。

MIT Licenseで公開します。商用利用・改変可。配布するコードには、同梱の著作権表示と許諾文を残してください。

普通の送信ボタンに毎回3秒の長押しを求めるのは、ちょっと待たせすぎ。ゲームのチャージ操作や、作品サイトの起動演出あたりなら、この大げささを楽しめそうです。今回のソースには送信処理は入れていません。

まずは、光を借りて返すところまで。UI Labには、こういう「使いどころを考える前に、触ってみたくなるもの」も増やしていきます。