input[type=range]で範囲指定っぽいUI

input[type=range]要素を組み合わせると、なんちゃって範囲指定のUIが構築できそうなのでやってみた。
開始値を指定するスライダーと、終了値を指定するスライダーが連動して、開始値が終了値より先にいったりしないよう制限もかけている。
実際に動作するサンプルはStackblitzで作ったページを参照: https://react-ts-keurq5.stackblitz.io
大事なのはdatalist要素で範囲を定義しておくこと。 単純にinput[type=range]要素でmax属性とmin属性で最大値・最小値または連動するように互いのvalueを参照し合うよう指定しても、想像した任意の値の範囲を指定するようなUIにはならない。 正確に言えば、開始値が終了値を追い越さないようになっているし開始値と終了値を指定する機能自体は満たしているけど、スライダーの値の範囲がぐちゃぐちゃ可変してしまって使いづらい。
そこで、datalistで値の範囲を定義して、2つのinput[type=range]要素がどちらも同じ値の範囲を持つスライダーになるようにした。 開始値のスライダーが終了値のスライダーより大きい値にならないように仕込むには、max,min属性を使っていけなくて、onChangeイベントで引っ掛ける必要があった。
以下にコードを貼り付けておく。 今回はReactで作ったが、やっていることは単純なので、他のフレームワークや素のJavaScriptでもすぐ書ける。
import React, { Component } from "react"; import { render } from "react-dom"; import "./style.css"; interface AppProps { startAt: number; endAt: number; callback?: (range: AppState) => any; } interface AppState { startAt: number; endAt: number; } class App extends Component<AppProps, AppState> { constructor(props) { super(props); this.state = { startAt: this.props.startAt, endAt: this.props.endAt }; } render() { const onChangeStart = (event: React.ChangeEvent<HTMLInputElement>) => { const startAt = parseInt(event.target.value); // 終了値を超えないように if (startAt < this.state.endAt) { this.setState({ startAt }, () => { this.props.callback(this.state); }); } }; const onChangeEnd = (event: React.ChangeEvent<HTMLInputElement>) => { const endAt = parseInt(event.target.value); // 開始値を下回らないように if (endAt > this.state.startAt) { this.setState({ endAt }, () => { this.props.callback(this.state); }); } }; // option要素で値の範囲を定義しないと、範囲が固定にならない。 const options = []; for (let i = this.props.startAt; i <= this.props.endAt; i++) { options.push(<option key={i} value={i} />); } return ( <div> <h1>input[type=range]で範囲指定っぽいUI</h1> <p>スライダーをドラッグして開始と終了をそれぞれ指定する</p> <datalist id="datalist">{options}</datalist> <p> 値の範囲: {this.state.startAt} 〜 {this.state.endAt} </p> <div> 開始: <input type="range" list="datalist" min={this.props.startAt} max={this.props.endAt} value={this.state.startAt} onChange={onChangeStart} /> <br /> 終了: <input type="range" list="datalist" min={this.props.startAt} max={this.props.endAt} value={this.state.endAt} onChange={onChangeEnd} /> </div> </div> ); } } const logging = value => { console.log(value); }; render( <App startAt={0} endAt={120} callback={logging} />, document.getElementById("root") );
stackblitz上のソースコードは https://stackblitz.com/edit/react-ts-keurq5?file=index.tsx