概要
<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向けの実装は入っていない。
