これでできる! クロスブラウザJavaScript入門

第23回 JavaScriptによるUIの実装:スライダー

この記事を読むのに必要な時間:およそ 4 分

HTML5のスライダーとの統合

さて,スライダーを汎用的にするに当たって,HTML5で新たにフォームのinput要素のタイプとして追加されたrangeについて触れておきましょう。

HTML5の<input type="range">は前述のようなスライダーをネイティブに提供してくれます。type=rangeはOperaやChromeやSafariなどで既に実装されています。

(range対応ブラウザではスライダーが表示されます)

今回はこのtype="range"になるべく合わせるように実装してみます(ただし,この記事ではtype=rangeが持っているstepや,stepDown(),stepUp()などのメソッドはサポートしませんし,横へのスライド限定です。是非,それらに挑戦してみてください⁠⁠。

汎用版スライダーのHTML

<div id="js-slider2">
  <form class="js-slider-form" onsubmit="return false;">
    <input type="range">
  </form>
  <form class="js-slider-form" onsubmit="return false;">
    <input type="range" min="100" max="400" value="200"
       style="width:300px;" >
  </form>
</div>

汎用版スライダーのJavaScript

function html5_slider(node){

var addEvent = document.addEventListener ?
  function(node,type,listener){
    node.addEventListener(type,listener,false);
  } :
  function(node,type,listener){
    node.attachEvent('on'+type, listener);
  }
var removeEvent = document.removeEventListener ?
  function(node,type,listener){
    node.removeEventListener(type,listener,false);
  } :
  function(node,type,listener){
    node.detachEvent('on'+type, listener);
  }

var dragging = false;
var slider, button, input;
var min = 0, max = 100;
var value = 0;
function set_value(){
  button.style.left = (value - button.clientWidth/2) +'px';
  input.value = min + value;
}
if (!node){
  node = document;
}
var inputs = node.getElementsByTagName('input');
// getElementsByTagNameで取得した要素を走査中に置き換えると
// 繰り上がりされるので、繰り上がりが起きても良いように、
// 後ろから走査する
var n = inputs.length;
while (n){
  n--;
  var _input = inputs[n];
  var type = _input.getAttribute('type');
  if(type === 'range' && !('step' in _input)){
    init(_input);
  }
}
function init(_input){
  var parent = _input.parentNode;
  var _min = parseInt(_input.getAttribute('min'), 10)||0;
  var _max = parseInt(_input.getAttribute('max'), 10)||100;
  value = parseInt(_input.value, 10) - _min || 0;

  var outer = document.createElement('div');
  var inner = document.createElement('div');
  var _button = document.createElement('input');
  _button.type = 'button';
  outer.className = 'js-slider';
  outer.style.width = (_max - _min)+ 'px';
  parent.insertBefore(outer, _input);
  outer.appendChild(inner);
  outer.appendChild(_button);
  if(window.ActiveXObject){
    // IEはinputのtypeをhiddenに書き換えることができない
    // 少々強引だが、outerHTMLについて置換を行う
    parent.removeChild(_input);
    _input = document.createElement(
      _input.outerHTML.replace('type=range', 'type=hidden')
    );
    parent.insertBefore(_input, outer.nextSbling || null);
  } else {
    _input.type = 'hidden';
  }
  button = _button;
  input = _input;
  set_value();
  addEvent(outer,'click', function(evt){
    dragging = true;
    min = _min;
    max = _max;
    input = _input;
    button = _button;
    slider = outer;
    mousemove(evt);
  });
  addEvent(_button,'mousedown', function(evt){
    if(!evt){
      evt = window.event;
    }
    dragging = true;
    min = _min;
    max = _max;
    input = _input;
    button = _button;
    slider = outer;
    if (evt.preventDefault){
      evt.preventDefault();
    } else {
      evt.returnValue = false;
    }
    addEvent(document, 'mousemove', mousemove);
  });
}
addEvent(document, 'mouseup', function(evt){
  if(dragging){
    dragging = false;
    removeEvent(document, 'mousemove', mousemove);
    input = null;
    button = null;
    slider = null;
  }
});
function mousemove(evt){
  if(!evt){
    evt = window.event;
  }
  if(dragging){
    var left = evt.clientX;
    var rect = slider.getBoundingClientRect();
    var width = button.clientWidth / 2;
    // マウス座標とスライダーの位置関係で値を決める
    value = Math.round(left - rect.left - width);
    // スライダーからはみ出したとき
    if (value < 0) {
      value = 0;
    } else if (value > slider.clientWidth) {
      value = slider.clientWidth;
    }
    set_value();
    if (evt.preventDefault){
      evt.preventDefault();
    } else {
      evt.returnValue = false;
    }
  }
}
}

いくつかポイントがありますが,まず,!('step' in _input) でrangeをサポートしているか簡易チェックしています。rangeをサポートしているブラウザではそのままrangeを使うようにします。そのため,ChromeやSafariではネイティブのスライダーが表示されていると思います。

また,IEは一度作ったinput要素のtypeをあとからhiddenにすることができません。document.createElement('<input type="hidden">') のようにして最初からhiddenな要素として作る必要があります(ちなみに,createElementにタグ名でなくタグ全体を書くのもIEの独自拡張です⁠⁠。

まとめ

今回はJavaScriptを使ったUIとしてスライダーパーツの実装方法を紹介しました。こちらもまだまだ改良の余地があるので,是非自分なり(もしくはHTML5の仕様にそって)に改良してみてください。

次回も引き続きJavaScriptを使ったクロスブラウザなUIの実装を見ていきたいと思います。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/