スタイルシート ルーレット の説明
戻る


JavaScript を使わず,スタイルシートだけでルーレットを作ってみます.
全くのお遊びです.これが何か実用の役に立つようなことは多分無いでしょう.スタイルシートは本来,動作を記述するものではありませんので,かなり力ずくで作っています.
画面
(これはキャプチャ画像です.これで遊ぶことはできません.)
使い方と仕組みについて説明します.


使い方

「選択肢数」で選択肢の数を選びます.

盤面の選択肢をクリックすると,その選択肢のテキストを入力できる状態になるので,テキストの文字列を入力します.
選択肢入力 1
選択肢入力 2
選択肢以外の場所をクリックすると入力を終了します.

「スタート」をクリックするとルーレットが回転を始めます.
スタートすると「スタート」ボタンの表示が「ストップ」に変わります.「ストップ」をクリックするとルーレットが次第に減速し,停止します.



回転のさせ方

transformrotate 関数でルーレットの盤面を回転させます.
このような指定で,カスタム プロパティの --theta1--theta2 の値を加算した角度の回転をさせます.
transform:rotate(calc((var(--theta1) + var(--theta2)) * 1deg));
--theta1 は「スタート」をクリックしてから「ストップ」をクリックするまでの間の等速回転時の角度で,--theta2 は「ストップ」をクリックしてから停止するまでの減速時の角度です.
「スタート」から「ストップ」までの間は,次のようなアニメーションで 360 度の回転を継続して行うようにします.(実際のプログラム(?)では,二度目以降に回転させたときに再度初期位置から回転を始めるよう,これとは少し異なった処理になっています.)
@keyframes rotate {
  from {
    --theta1:0;
  }
  to {
    --theta1:360;
  }
}

animation:rotate 3s linear infinite;
「ストップ」をクリックしたら,等速回転のアニメーションを停止します.
animation:rotate 3s linear infinite paused;
そして,次のようなアニメーションで,カスタム プロパティ --decel-t の値の時間(秒)で停止させます.
--theta2:calc(var(--decel-t) * 60);
transition:--theta2 calc(var(--decel-t) * 1s) cubic-bezier(0.3333333, 0.6666667, 0.6666667, 1);
初めは速く減速して,だんだん減速の仕方がゆっくりになるように,タイミング関数にベジェ曲線を使っています.
cubic-bezier(0.3333333, 0.6666667, 0.6666667, 1)
このベジェ曲線は放物線になります.つまり,回転の角度が放物線のグラフに沿って増加します.
放物線のグラフ
等速回転時は 3 秒で 1 周,つまり 120 度/秒 の角速度で回転しています.私の理解が正しければ,放物線に沿って減速させる場合,減速時間が T 秒なら停止するまでに T × 60 度 回転するように設定すれば,減速開始時の角速度が等速時と同じ 120 度/秒 になるはずです.つまり,等速から減速に滑らかに移行します.


減速時間のランダム化

「ストップ」をクリックしてからルーレットが停止するまでの時間がばらつくよう,以下のような処理をしています.

初めは次のような方法を考えたのですが,これはうまくいきませんでした.

初めに考えた方法

スタートしたら,減速時間のプロパティの値が一定の範囲で繰り返すアニメーションを動かします.
たとえば 1 秒間で 10〜20 の値を取るようにするなら
@keyframes decel-t {
  from {
    --decel-t:10;
  }
  to {
    --decel-t:20;
  }
}

animation:decel-t 1s linear infinite;
のようにします.
「ストップ」をクリックしたらアニメーションを停止して,プロパティの値を固定します.
そして,そのアニメーションを設定した HTML 要素の子孫の要素で,プロパティの値を継承して減速のアニメーションを行います.
--theta2:calc(var(--decel-t) * 60);
transition:--theta2 calc(var(--decel-t) * 1s) cubic-bezier(0.3333333, 0.6666667, 0.6666667, 1);
こうすれば,「ストップ」をクリックするタイミングによって異なる減速時間が使われます.

ところが,この方法だと減速のアニメーションが動きませんでした.
アニメーションで減速時間を設定するのではなく,単純に
--decel-t:10;
などのように設定した場合は,問題なく動きます.
また,アニメーションで設定した減速時間をアニメーション以外に使うことは問題ありませんでした.
たとえば
--theta2:calc(var(--decel-t) * 60);
とすれば,--theta2 は正しく設定されます.
どうも,アニメーションで設定したプロパティの値を他のアニメーションのパラメータとして使うと,うまく動かないようです.transition-duration などのアニメーションのプロパティのタイプはアニメーション不可なので,このような使い方もできないのではないかと思います.

この方法がうまくいかなかったので,代替の方法として以下のようにしました.

最終的に採用した方法

減速時間を一定の範囲で連続的に変化させる代わりに,数種類のものに限定します.実際のプログラムでは 16,18,20,22,24 の 5 種類としました.
そして,その減速時間に対応する数値を 16 → 0,18 → 1,20 → 2,… のように決めて,アニメーションで変化させます.
@keyframes decel {
  from {
    --decel:0;
  }
  to {
    --decel:4;
  }
}

animation:decel 1s steps(5, jump-none) infinite;
減速時間の種類と同じ個数の「ストップ」ボタンを用意して,各減速時間と対応させます.それを同じ位置に重ねて表示して,一番前面にある物だけが見えている状態にします.
ストップ ボタンの重なり 1
アニメーションで設定された減速時間に合わせて「ストップ」ボタンの Z オーダーを変え,該当の「ストップ」ボタンが前面になるようにします.
その状態でボタンをクリックすると,前面のボタンがクリックされ,そのボタンに対応するセレクタが有効になります.
ストップ ボタンの重なり 2
各ボタンのセレクタで,該当の減速時間を設定して減速の処理を開始します.

なお,初めに考えた方法では,「スタート」から「ストップ」までの間,減速時間をアニメーションしますが,こちらの方法では,減速時間を選ぶ数値を「ストップ」から次の「スタート」までの間アニメーションします.
初めの方法では,「ストップ」から次の「スタート」までの間は減速時間が変わらないように,減速時間のアニメーションを停止しておく必要があります.
一方,こちらの方法では,どの「ストップ」ボタンをクリックしたかで減速時間が決まるので,クリックした後は減速時間を選ぶ数値の方は変化しても構いません.その一方で,「ストップ」をクリックしようとしているときにボタンの重なり方が頻繁に変わると,クリックがしにくくなります.
そのような理由から,減速時間を選ぶアニメーションは「ストップ」から次の「スタート」までの間に行うようにしました.


カスタム プロパティの定義

このプログラムでは,いくつかのカスタム プロパティを @ルールの @property で定義しています.
@property の定義は,たとえばこのように書きます.
@property --a {
  syntax:'<integer>';
  inherits:true;
  initial-value:0;
}
このプログラムでは,カスタム プロパティにアニメーション(transition も含む)を使っているところがあります.カスタム プロパティにアニメーションを使うためには,そのプロパティがアニメーション可能である必要があります.
@propertysyntax<number><integer> を指定することで,そのプロパティをアニメーション可能にしています.

また,計算結果の小数を整数に丸めるためにも @property を使っています.整数のプロパティを定義して,計算結果をそのプロパティに設定することで,整数への丸めを行っています.


このおもちゃは Opera 80 以上用に作っていますが,Google Chrome 107 で動くことが確認できています.Google Chrome の他のバージョンでの動作は未確認です.
Firefox では,バージョン 105 の時点でまだ @property が未実装なので,動きません.


戻る