概要
<input>
要素のrange
型を利用して「◯◯〜◯◯」のような範囲の選択ができるレンジスライダーを作った。
ポイントは、
-
range
型をz軸に2つ重ねる - 手前のスライダーで奥になるスライダーの選択色を隠す
背景と課題
スライダーで値を選択する<input>
要素としてrange
型がある。これはつまみ(thumb)が1つのスライダーで、thumbを動かすことで値を選択できるが、「◯◯〜◯◯」といった選択ができない。
そこで、直感的にもわかりやすく、thumbが1つだけのスライダーと同じ大きさでthumbを2つ持つスライダーを実現する。
実装
完成形
前提となるフレームワーク
- jQuery
- Bootstrap
実装は以下のとおりです。
<div class="multi-slider">
<input type="range" class="slider-lower" value="30" min="0" max="100" data-pb-color="var(--bs-secondary-bg)">
<input type="range" class="slider-upper" value="70" min="0" max="100" data-pb-color="var(--bs-primary)">
</div>
.slider-lower, .slider-upper {
-webkit-appearance: none;
appearance: none;
cusor: pointer;
outline: none;
height: 8px;
border-radius: 8px;
background: var(--bs-secondary-bg);
}
.multi-slider ::-webkit-slider-thumb {
background-color: var(--bs-primary);
pointer-events: auto;
}
.multi-slider {
display: grid;
padding: 8px;
width: 100%;
/* thumb以外の操作を無効にする */
pointer-events: none;
}
.slider-lower, .slider-upper {
grid-column: 1;
grid-row: 2;
}
.slider-lower {
z-index: 1;
}
$(function(){
$('.slider-lower, .slider-upper').on('load input', function(e){
if ( Number($('.slider-lower').val()) >= Number($('.slider-upper').val()) ) {
if ( $(this).hasClass('slider-lower') ){
$(this).val(Number($('.slider-upper').val())-1);
} else {
$(this).val(Number($('.slider-lower').val())+1);
}
}
ratio = ($(this).val()/$(this).prop("max"))*100;
left = $(this).data('pb-color');
if ( $(this).hasClass('slider-lower') ){
right = 'rgba(255,255,255,0.0)';
} else {
right = 'var(--bs-secondary-bg)';
}
$(this).css('background','linear-gradient(90deg, '+left+' '+ratio+'%, '+right+' '+ratio+'%)');
});
$('.slider-lower, .slider-upper').trigger('load');
});
実現方法の概略
<input>
要素のrange
型は、要素と-webkit-slider-runnable-track
、-webkit-slider-thumb
という2つの疑似要素から構成される。
基本的なアイディアは、まず、図のような2つのスライダーを用意する。
slider-lower
は最小値を選択するスライダーで、thumbの左を背景色に右を透明にする。slider-upper
は最大値を選択するスライダーで、左を選択色に右を背景色にする。このようにthumbを境界にtrackを塗り分けたスライダーをslider-lower
が手前になるように配置することで、最小値から最大値の間だけが選択色が見えるようにする。
具体的な実装の説明
trackの塗り分け
trackの塗り分けはCSSで設定する方法とJavaScriptで設定する方法がある。今回は、調整が用意なためJavaScriptを使う。まず、ベースとなるスタイルを設定する。
.slider-lower, .slider-upper {
-webkit-appearance: none;
appearance: none;
cusor: pointer;
outline: none;
height: 8px;
border-radius: 8px;
background: var(--bs-secondary-bg);
}
.multi-slider ::-webkit-slider-thumb {
background-color: var(--bs-primary);
}
要素自体、ここではslider-lower
とslider-upper
にスタイルを設定する。-webkit-appearance: none;
とappearance: none;
でウィジェットの機能を非表示にし、以下基本的なスタイルを設定する。
cursor: pointer;
でカーソルがあたったときの形を設定する。
outline: none;
でフォーカスがあたったときに枠を表示させない。
height
, border-radius
でtrackの高さと端のスタイルを設定する。
background
でtrackの背景色を設定する。--bs-secodary-bg
としたのはBootstrapに合わせるため。
.multi-slider ::webkit-slider-thumbthumb{}
では、thumbのスタイルを設定する。ウィジェットの標準スタイルで十分だが、ここでは選択色と合わせるためにbackground-color
に--bs-primary
を設定する。
次にJavaScriptで塗り分ける。
スクリプトは前掲のものを再掲する。
$(function(){
$('.slider-lower, .slider-upper').on('load input', function(e){
if ( Number($('.slider-lower').val()) >= Number($('.slider-upper').val()) ) {
if ( $(this).hasClass('slider-lower') ){
$(this).val(Number($('.slider-upper').val())-1);
} else {
$(this).val(Number($('.slider-lower').val())+1);
}
}
ratio = ($(this).val()/$(this).prop("max"))*100;
left = $(this).data('pb-color');
if ( $(this).hasClass('slider-lower') ){
right = 'rgba(255,255,255,0.0)';
} else {
right = 'var(--bs-secondary-bg)';
}
$(this).css('background','linear-gradient(90deg, '+left+' '+ratio+'%, '+right+' '+ratio+'%)');
});
$('.slider-lower, .slider-upper').trigger('load');
});
スクリプトはslider-lower
またはslider-upper
がload
またはthumbを動かされたときに実行される。
最初のif
文は、左右のthumbが逆転しないように制御している。
次は、thumbの位置を求めratio
に持つ。
次に、thumbの左側の色をleft
に、右側の色をright
に持つ。左側の色は、HTMLで<input>
要素のdata-pb-color
属性に持たせている。
最後に、要素の背景色をlinear-gradient
で2色に塗り分ける。
なお、本質からは外れるが、読み込み時にload
が発火されなかったのでtrigger('load')
で強制的に発火させている。
2つのスライダーを重ねる
.multi-slider {
display: grid;
padding: 8px;
width: 100%;
}
.slider-lower, .slider-upper {
grid-column: 1;
grid-row: 2;
}
.slider-lower {
z-index: 1;
}
2つのスライダーをラップするdiv
要素(multi-slider
)をdisplay: grid;
でグリッドコンテナ化し、slider-lower
とslider-upper
を同じセルに配置する。また、slider-lower
をz-index:1;
で手前に配置する。
これで見た目は完成形と同じになるが、右側のthumbを操作できないので、pointer-events
を使って以下のように設定する。
.multi-slider ::-webkit-slider-thumb {
pointer-events: auto;
}
.multi-slider {
pointer-events: none;
}
multi-slider
全体をpointer-events: none;
でポインターを無効にする。これはtrackをクリックしたときにthumbが移動するのを防ぐ。
次に、multi-slider
要素の中の-webkit-slider-thumb
疑似要素だけにpointer-events: auto;
で操作権を与える。
まとめ
range
型の<input>
属性を2つ重ねることで、「◯◯〜◯◯」といった範囲選択ができるレンジスライダーを作った。選択範囲と背景の塗り分けをCSSとJavaScriptを使って実装した。なお、今回の実装では-webkit付きの疑似要素を使っておりFirefox向けの実装は入っていない。