Implement double-ended range slider component
This commit is contained in:
parent
a2c9c8f2ee
commit
a7b05af1f0
156
frontend/src/components/form/DoubleRangeSlider.svelte
Normal file
156
frontend/src/components/form/DoubleRangeSlider.svelte
Normal file
@ -0,0 +1,156 @@
|
||||
<section>
|
||||
<span class="form-label">{label}</span>
|
||||
<div class="line" bind:clientWidth={width}>
|
||||
<div class="slider" bind:this={leftParent} on:mousedown={onMouseDown} on:touchstart={onMouseDown} style="
|
||||
left: min({rightParent?.style?.left || `${width}px`}, max(0px, min({width-(sliderDiameter/2)}px, {leftOffset}px)));
|
||||
">
|
||||
<div bind:this={leftSlider} style="height: {sliderDiameter}px; width: {sliderDiameter}px">
|
||||
<span class="label" bind:this={leftLabel}>{start}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="slider" bind:this={rightParent} on:mousedown={onMouseDown} on:touchstart={onMouseDown} style="
|
||||
left: max({leftParent?.style?.left || `${width}px`}, max(0px, min({width-(sliderDiameter/2)}px, {rightOffset}px)));
|
||||
">
|
||||
<div bind:this={rightSlider} style="height: {sliderDiameter}px; width: {sliderDiameter}px">
|
||||
<span class="label" bind:this={rightLabel}>{end}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<svelte:window
|
||||
on:mouseup={onMouseUp}
|
||||
on:touchend={onMouseUp}
|
||||
on:mousemove={onMouseMove}
|
||||
on:touchmove={onTouchMove}
|
||||
/>
|
||||
|
||||
<style>
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #3472f7;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.label {
|
||||
position: relative;
|
||||
top: -25px;
|
||||
left: 0;
|
||||
color: #9a9a9a;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.slider > div {
|
||||
position: relative;
|
||||
top: -8px;
|
||||
left: 0;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import {onMount} from "svelte";
|
||||
|
||||
export let label = "Slider";
|
||||
|
||||
export let min = 0;
|
||||
export let max = 100;
|
||||
|
||||
export let start;
|
||||
export let end;
|
||||
|
||||
const sliderDiameter = 20;
|
||||
|
||||
let leftSlider, rightSlider;
|
||||
let leftParent, rightParent;
|
||||
let leftLabel, rightLabel;
|
||||
let moving;
|
||||
|
||||
let prevWidth = -1;
|
||||
let width;
|
||||
let leftOffset = 0;
|
||||
let rightOffset = 0;
|
||||
|
||||
$: {
|
||||
if (prevWidth !== width) {
|
||||
leftOffset = (width - (sliderDiameter / 2)) * ((start - min) / (max - min));
|
||||
rightOffset = (width - (sliderDiameter / 2)) * ((end - min) / (max - min));
|
||||
}
|
||||
|
||||
prevWidth = width;
|
||||
}
|
||||
|
||||
function onMouseDown(e) {
|
||||
if (e.target === rightSlider || e.target === rightParent) {
|
||||
moving = rightSlider;
|
||||
} else if (e.target === leftSlider || e.target === leftParent) {
|
||||
moving = leftSlider;
|
||||
}
|
||||
}
|
||||
|
||||
let previousTouch;
|
||||
|
||||
function onTouchMove(e) {
|
||||
const touch = e.touches[0];
|
||||
|
||||
if (previousTouch && moving) {
|
||||
e.movementX = touch.pageX - previousTouch.pageX;
|
||||
onMouseMove(e);
|
||||
}
|
||||
|
||||
previousTouch = touch;
|
||||
}
|
||||
|
||||
function onMouseMove(e) {
|
||||
if (moving === rightSlider) {
|
||||
rightOffset += e.movementX;
|
||||
|
||||
const ratio = parseOffset(rightParent.style.left) / (width - (sliderDiameter / 2));
|
||||
end = Math.ceil(ratio * (max - min) + min);
|
||||
} else if (moving === leftSlider) {
|
||||
leftOffset += e.movementX;
|
||||
|
||||
const ratio = parseOffset(leftParent.style.left) / (width - (sliderDiameter / 2));
|
||||
start = Math.ceil(ratio * (max - min) + min);
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
moving = null;
|
||||
previousTouch = null;
|
||||
}
|
||||
|
||||
// calc(123px) -> 123
|
||||
function parseOffset(offset) {
|
||||
const regex = /calc\((\d+).*\)/;
|
||||
const match = offset.match(regex);
|
||||
if (match) {
|
||||
return parseInt(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!start) start = min;
|
||||
if (!end) end = max;
|
||||
|
||||
leftOffset = (width - (sliderDiameter / 2)) * ((start - min) / (max - min));
|
||||
rightOffset = (width - (sliderDiameter / 2)) * ((end - min) / (max - min));
|
||||
});
|
||||
</script>
|
Loading…
x
Reference in New Issue
Block a user