😈
2つの摘みを持ったレンジバーを作ってみた
個人開発のアプリの一環にゾゾタ⚪︎ンのような価格の絞り込み機能を作ってみたので共有します
参考にさせて頂いた動画
実際の使用感は以下のような感じ
bladeとコントローラー
home.blade.php
<form action="{{route('app.home.search')}}" method="get" class="flex items-center justify-start" id="searchForm">
<div>
<h2 class="rangeText">価格帯を選択</h2>
<div class="container">
{{-- low値を表示するコンテナ --}}
<div class="low-num-container">
<input type="number" id="low-num" value="0" disabled>
</div>
<div class="slider-container">
{{--価格帯のバー --}}
<div class="range-bar" id="range-bar"></div>
{{-- 二つのrangeを用意 --}}
<input type="range" name="highPrice" id="high" class="high" min="0" max="300000" step="5000" value="{{request('highPrice') ?? 300000}}">
<input type="range" name="lowPrice" id="low" class="low" min="0" max="300000" step="5000" value="{{request('lowPrice') ?? 0}}">
</div>
{{-- high値を表示するコンテナ --}}
<div class="high-num-container">
<input type="number"id="high-num" value="300000" disabled>
</div>
</div>
</div>
</form>
方針としてはrangeの丸みを二つとrangeバーを重ね合わせて一つのrangeバーのようにします
input:numberにrangeの値を入れることでどんな値かを表示してみます
value="{{request('highPrice') ?? 300000}}
で入力された項目を保持します
コントローラーは以下の通り(本題とずれているので軽く紹介)
HomeController.php
public function search(Request $request) : View
{
$lowPrice = $request->input('lowPrice', 0);
$highPrice = $request->input('highPrice', 300000);
$query = Part::query()->with(['reviews','category']);
// 価格帯検索
$query->whereBetween('price',[$lowPrice,$highPrice]);
// 取得
$parts = $query->get();
return view('app/home',['parts' => $parts,'categories' => $categories]);
}
css
priceBar.css
.container{
display: flex;
position: relative;
width: 600px;
height: 50px;
align-items: center;
justify-content: center;
border-radius: 20px;
background: #d1d5db;
}
.rangeText{
margin: 0 0 6px 10px;
}
.container input[type="number"]{
width: 80px;
height: 30px;
background: #fff;
border: 1px #eee solid;
font-size: 15px;
font-weight: 700;
text-align: center;
border-radius: 8px;
}
/* ブラウザにデフォルトでついてるnumberのスピンボタンを削除 */
.container input[type="number"]::-webkit-outer-spin-button,
.container input[type="number"]::-webkit-inner-spin-button{
-webkit-appearance: none;
}
.slider-container{
position: relative;
width: 400px;
height: 6px;
background: #eee;
outline: none;
margin: 10px;
}
.range-bar{
height: 100%;
border-radius: 50px;
background: #3e3e3e;
top: 50%;
transform: translateY(-50%);
position: absolute;
width: 100%;
}
.slider-container input[type="range"]{
position: absolute;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
-webkit-appearance: none;
outline: none;
background: none;
width: 100%;
}
.slider-container input[type="range"]::-webkit-slider-thumb{
pointer-events: all;
-webkit-appearance: none;
width: 16px;
height: 16px;
background: black;
border-radius: 50px;
border: 2px #fff solid
}
親のdivにrelative
とグレーの背景をつけておき、そこに選択された範囲の示す黒色のバーを重ね合わしています
webkit-appearance:none
をつけることでブラウザについてくるデフォルトの仕様をなくせます
js
jsでは以下の二つのことを考えます
- lowのrangeとhighのrangeが逆転しないようにする
- lowとhighの値を取得して.range-barの位置を更新する
priceBar.js
document.addEventListener('DOMContentLoaded',() => {
const lowSlider = document.getElementById('low');
const highSlider = document.getElementById('high');
const lowNum = document.getElementById('low-num');
const highNum = document.getElementById('high-num');
const rangeBar = document.getElementById('range-bar');
const form = document.getElementById('searchForm');
const minPrice = 0;
const maxPrice = 300000;
const step = 5000;
let timer;
function updateSlider(event) {
// intに変換
let lowVal = parseInt(lowSlider.value);
let highVal = parseInt(highSlider.value);
// low値とhigh値に下限、上限を持たせる low値は0~295000まで high値は5000~300000まで
lowVal = Math.max(minPrice,Math.min(lowVal,maxPrice - step));
highVal = Math.min(maxPrice,Math.max(highVal,minPrice + step));
// highとlowの差が逆転するとき
if (highVal - lowVal < step) {
// low > highになろうとしているとき
if (event.target === lowSlider) {
lowVal = highVal - step;
}
// high < lowになろうとしているとき
else {
highVal = lowVal + step;
}
}
// numberとrangeの更新
lowSlider.value = lowVal;
highSlider.value = highVal;
lowNum.value = lowVal;
highNum.value = highVal;
// range-barのmaxとminの位置を取得
const max = ( highVal / maxPrice ) * 100;
const min = ( lowVal / maxPrice ) * 100;
// range-barを更新
rangeBar.style.left = min + '%';
rangeBar.style.width = ( max - min ) + '%';
}
// レンジバー変更後に自動送信する関数 400ミリ秒立ったら自動で更新
function submit(){
clearTimeout(timer);
timer = setTimeout(() => {
form.submit();
},400)
}
// イベント設定
[lowSlider,highSlider].forEach(slider => {
slider.addEventListener('input',updateSlider);
slider.addEventListener('input',submit);
})
// 初期設定
updateSlider();
})
lowVal = Math.max(minPrice,Math.min(lowVal,maxPrice - step));
のところはなくても動くと思いますが一応明示しておきました。low値は最小値(0)を下回ることなく、最大値はの上限から -1ステップ分の(295000)まで high値も同様です
終わり
これで終わりです。多分動くはず
rangeの値をstep=5000で固定するのではなく、[5000,10000,20000,40000,...]のようにできたらより使いやすいのかと思ったけどできなかった。。。 🥺
Discussion