環状にグルグルできる数値入力UI
先に完成形
要件
- 円形上をドラッグすることで角度を数値として入力
- ドラッグした地点の角度を採用
- 値は範囲に制限なく増減可能
このUIが欲しい状況としては色々あると思われるが、今回は先に挙げたようにカラーピッカーの色相部分をグルグル入力できるようにしたかった。
要件1.と2.は単にドラッグした座標と円の中心をもってごにょごにょ角度を求めるだけなのでいいとして、3.を実現するのが意外と面倒だった。それをいい感じにしたかったというのがお題。
もう少し背景
色相が欲しいだけなら別に3.の要件は不要では?という疑問は確かにその通り。
今回作ったカラーピッカーはただ色を取得するだけでなくて、HSVAそれぞれをtransform
の値として取得したいという背景があった。
こんな感じに色相をtransform
の角度として表現して、2点間を補完したかった。
このとき-180 ~ 180で値がループしてしまうと、2点間補完ではループをジャンプすることができず巻き戻ってしまう。
角度の値を制限しない
3.を無視してとりあえず角度を入力できるようにすると、値の範囲は当然ながら-180 ~ 180 (求め方次第では0 ~ 360)という1周分に制限される。
現在このあたり(10くらい)にあるとして、
ここに移動したら、-10あたりということになる。その通り。
ここで実はユーザーはこういう操作をしていたとする。
このときユーザーが期待している角度は-10ではなく350のはずである。しかし幾何学的にカーソルと中心座標からただ角度を求めるだけでは-10と350を区別することはできない。何からの工夫が求められる。
対策
中心座標は定数として、変更後の角度が-10か350かを判別するには現在のカーソル位置だけでは足りない。
ということでもう一つ変数が必要となる。残念ながらステートレスに綺麗に一発では求まらない。UIの宿命である。
今回採用したのは「ワンステップ前の角度」。ドラッグで操作しているなら直前のカーソル位置から求めた角度、クリックで操作しているなら前回クリック時のカーソル位置から求めた角度。
そのワンステップ前の角度に対して、新たな角度が180度以内に収まっているような周回の角度を採用する。
こんな感じ、伝わる。
例えば前回が30だったのなら、350は180を超えているので不採用。-10は収まっているので採用。
逆に前回が200だったのなら、350が180に収まるので採用する。
2つの候補がぴったり180になってしまったら好きな方を採用する。細かいことは気にしない。
手順
- ワンステップ前の角度(t0)を正規化(-180 ~ 180などの範囲)しておく(=> nt0)
- 今回の角度を求めて同じく正規化する(=> nt1)
- nt0から180度以内に収まるようnt1の周回を調整する(=> ant1)
- t0 - nt0 + ant1を新たな角度として採用する
こうすることでグルグルできるUIが完成する。
妥協点
ドラッグ時は何も違和感ないが、クリック時に180度以内の角度を本当にユーザーが求めているとは限らない。もしかしたら-10のつもりでクリックしたかもしれないし、350のつもりでクリックしたのかもしれない。
こればかりはいい解決法が思い浮かばない。脳波コントロールが待たれる。
Discussion