multi range 自作しました
- カテゴリ:
- JavaScript
- つくった
- コメント数:
- Comments: 0
◆ multirange がメンテされてない
◆ Chrome 83 の問題がそのまま
◆ この機能が polyfill でなくなって作者のやる気もなさそう
◆ とりあえずの対応したけど メンテされないなら自作しよう
◆ Chrome 83 の問題がそのまま
◆ この機能が polyfill でなくなって作者のやる気もなさそう
◆ とりあえずの対応したけど メンテされないなら自作しよう
multirange
input の type="range" を使うとレンジバーが表示されて スクロールバーのようにマウスで掴んで動かして範囲内の値を選択できます0 から 100 から 1 つ選ぶみたいなときは便利なのですが 範囲を選びたいときにはデフォルトのものでは使えません
multirange というライブラリを使えばマウスでつかめる箇所が 2 つになって範囲選択ができるようになります
今回これを使おうとしたのですが…… Chrome 83 の仕様変更の対応がまだされてませんでした
Chrome 83 では input 系の見た目が一新されてレンジバーでは選択してる部分より左側には色がつくようになりました
そのせいで multirange もわかりづらくこうなってしまいます
Github を見ても issue はできてるもののコメントは一切なく 全体の最終コミットも半年ほど前です
それに元々 multi range は標準機能の予定がありこのライブラリは Polyfill だったそうですが 今では標準機能から除外されて Polyfill ではなくなっています
そのこともあり 作者の人がメンテに興味がないみたいな書き込みもしています
修正される期待はあまりできない状況です
CSS 対応
色がついた部分を透明にできれば元通り使えるかなと CSS を色々いじってみたのですが webkit プレフィックスがつくようなブラウザ独自のスタイルで調整するもので 今のところは Chrome83 から色がついたところだけを調整することはできなそうでした-webkit-appearance を none にすると色は消えるのですがスライド可能なエリア全体が非表示になります
色がつく左側もつかない右側も非表示で 動かすことのできる●だけが残ります
この状態にスライド可能なエリアを表示だけできればいいかなと 背景色を使って選択してないエリアを表現すれば一応それらしくはなりました
この CSS を追加します
.multirange {
background: #e0e0e0;
height: 6px;
-webkit-appearance: none;
}
自作版
一応これで完了だったのですが なんか WebComponent にしたいなぁ とかメンテされなさそうなのを今から使うのもなぁ という気持ちがあります機能的にはたいしたことしてないので 一から WebComponent で自作しました
見た目はだいたい一緒です
値の取得と設定はプロパティのみのサポートで from と to を使います
両方まとめては value です
バーの両端は min と max に指定します
const mr = document.querySelector("multi-range")
mr.from
// 20
mr.to
// 80
mr.value
// 20,80
mr.value = "30,70"
mr.from
// 30
mr.to
// 70
<!doctype html>
<script>
customElements.define("multi-range", class extends HTMLElement {
connectedCallback() {
if (this.shadowRoot) return
this.attachShadow({mode: "open"}).innerHTML = `
<style>
:host {
display: inline-block;
width: 300px;
--line-inactive: #ddd;
--line-active: deepskyblue;
--thumb: dodgerblue;
}
input[type="range"] {
width: 100%;
height: 30px;
cursor: pointer;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-runnable-track {
height: 8px;
}
input[type="range"]::-webkit-slider-thumb {
height: 24px;
width: 24px;
margin-top: -8px;
background: var(--thumb);
border-radius: 50%;
box-shadow: 0 0 5px 0 #0003;
-webkit-appearance: none;
}
.container {
width: 100%;
position: relative;
}
#back, #front {
position: absolute;
top: 0;
left: 0;
}
#back::-webkit-slider-thumb,
#front::-webkit-slider-thumb {
position: relative;
z-index: 1;
}
#back::-webkit-slider-runnable-track {
background: linear-gradient(to right, var(--line-inactive) 0% var(--min),
var(--line-active) var(--min) var(--max),
var(--line-inactive) var(--max) 100%);
}
#front,
#front::-webkit-slider-runnable-track {
background: transparent;
}
</style>
<div class="container">
<input id="back" type="range" min="0" max="100" value="20" />
<input id="front" type="range" min="0" max="100" value="80" />
</div>
`
this._elems = {
back: this.shadowRoot.getElementById("back"),
front: this.shadowRoot.getElementById("front"),
}
this._elems.back.addEventListener("input", () => { this._redraw() })
this._elems.front.addEventListener("input", () => { this._redraw() })
this._redraw()
}
_redraw() {
const { min, max, from, to } = this
const x = (from - min) / (max - min) * 100
const y = (to - min) / (max - min) * 100
this._elems.back.style.setProperty("--min", x + "%")
this._elems.back.style.setProperty("--max", y + "%")
}
get min() {
return this._elems.back.min
}
set min(value) {
const [min, max] = [+value || 0, this.max].sort((a, b) => a - b)
this._elems.back.min = min
this._elems.back.max = max
this._elems.front.min = min
this._elems.front.max = max
this._redraw()
}
get max() {
return this._elems.back.max
}
set max(value) {
const [min, max] = [this.min, +value || 0].sort((a, b) => a - b)
this._elems.back.min = min
this._elems.back.max = max
this._elems.front.min = min
this._elems.front.max = max
this._redraw()
}
get from() {
return Math.min(this._elems.back.value, this._elems.front.value)
}
set from(value) {
const [min, max] = [+value || 0, this.to].sort((a, b) => a - b)
this._elems.back.value = min
this._elems.front.value = max
this._redraw()
}
get to() {
return Math.max(this._elems.back.value, this._elems.front.value)
}
set to(value) {
const [min, max] = [this.from, +value || 0].sort((a, b) => a - b)
this._elems.back.value = min
this._elems.front.value = max
this._redraw()
}
get value() {
return [this._elems.back.value, this._elems.front.value].sort((a, b) => a - b).join(",")
}
set value(value) {
const [min, max] = value.split(",").slice(0, 2).sort((a, b) => a - b)
this._elems.back.value = min
this._elems.front.value = max
this._redraw()
}
})
</script>
<multi-range></multi-range>