スクロールバーをつくるときのポイント
スクロールバーやスライダーってなかなか使いまわせないし、決定版ができないですよね。
いっこいっこ見た目や機能が微妙に違うし、連動させる対象や表現も違います。
個人的にも何度Scrollbar.asを作ったかわかりませんが、何回か作ることで方向性が見えてきた部分もあります。
実装方法というよりその考えかたの部分を書いてみたいと思います。
見た目を無視する=数値化する=抽象化する
まず見た目が一番ややこしいので、まず忘れます。
つまりスクロールバーの本質は、コンテンツをスライドさせるものではなくて、0%~100%(0.0-1.0)の値をスライドさせているものだと捉えます。こういう数値としてしか見ない見方を、抽象化するといいます。
本質的に、やりたいことはある範囲を限界を超えないで行ったり来たりする操作です。表現としてスライドするかぐるぐる回るかといったことは、その具合を具体的に反映した結果でしかありません。
もう少し抽象的な話を続けます。
0%-100%(0.0-1.0)の考え方ができると次のように展開できます。
- 0~-100% のスクロールバー
- -500px~500px のスライド
- -180度~180度 の回転
- 1000px~2000px のスライド(オフセット付)
- 50%~150% の拡大率
見た目に話を戻す=具体化する
これらをスライダ単品の値と、利用シーンでの値の反映に分けて考えると次のようになります(percent = 0.0-1.0)
- 0~-100% のスクロールバー → var newPercent:Number = percent × -1
- -500px~500px のスライド → contentsMc.x = percent × 1000 -500
- -180度~180度 の回転 → contentsMc.rotateX = percent × 360 -180
- 1000px~2000px のスライド(オフセット付) → contentsMc.x = percent × 1000 +1000
- 50%~150% の拡大率 → contentsMc.scaleX = contentsMc.scaleY = percent+0.5
これでスクロールバーやスライダー自体は反映先のことをあまり考えずに計算部分を分離することができます。
反映対象だけでなく、イージングなどの演出も見た目の要素内でやればよいのでスライダの内部計算には影響ありません。
逆にスクロールバーの操作が、スライドバー、上下ボタン、ホイールなどと複雑になっても、入力は常にパーセントを変化させるようにすれば、パーセントに応じてスクロールバーのほうに反映させるだけなので(たぶん)すっきりかけることでしょう。
Flashのサンプルです。
import flash.display.*; import flash.events.*; import flash.geom.*; var _range:Number = bg.height - bar.height; var _position:Number = 0; var _percent:Number = 0; // 初期化 bar.y = bg.y; // スライドのイベントリスナー設定 bar.addEventListener( MouseEvent.MOUSE_DOWN, onPressBar ); bar.addEventListener( MouseEvent.MOUSE_UP, onReleaseBar ); bar.stage.addEventListener( MouseEvent.MOUSE_UP, onReleaseBar ); function onPressBar( e:Event ):void { bar.startDrag( false, new Rectangle( bg.x, bg.y, 0, _range ) ); addEventListener( Event.ENTER_FRAME, onUpdateBar ); } function onReleaseBar(e:Event):void { bar.stopDrag(); removeEventListener( Event.ENTER_FRAME, onUpdateBar ); } // パーセント値を更新 function onUpdateBar(e:Event):void { _position = bar.y - bg.y; _percent = _position / _range; } addEventListener( Event.ENTER_FRAME, onUpdateKonchi ); // パーセント値を具体的に反映する var _offsetKonchiY:Number = 100; var _rangeKonchiY:Number = 90; var _offsetKonchiScale:Number = 1.2; var _rangeKonchiScale:Number = -1; function onUpdateKonchi( e:Event ):void { konchi.y = _percent * _rangeKonchiY + _rangeKonchiY; konchi.scaleX = konchi.scaleY = _percent * _rangeKonchiScale + _offsetKonchiScale; } addEventListener( Event.ENTER_FRAME, onUpdateValue ); // 値を表示に反映する function onUpdateValue( e:Event ):void { bg_height.text = String( bg.height ); bar_height.text = String( bar.height ); range.text = String(_range ); bg_y.text = String(Math.round( bg.y ) ); bar_y.text = String(Math.round( bar.y ) ); position.text = String( Math.round( _position) ); percent.text = String( Math.round( _percent*100 ) ); konchi_y.text = String( Math.round( konchi.y ) ); konchi_scale.text =String( Math.round( konchi.scaleX *100) ); }