Skip to content

Instantly share code, notes, and snippets.

@dgreenway
Created January 14, 2021 00:15
Show Gist options
  • Save dgreenway/0dd13069a92ab7a15424a5a0e048f960 to your computer and use it in GitHub Desktop.
Save dgreenway/0dd13069a92ab7a15424a5a0e048f960 to your computer and use it in GitHub Desktop.
dual range slider - js based
<h1>Class Times</h1>
<div class="dual-range" data-min="0" data-max="24">
<input type="hidden" data-from="" data-to="">
<span class="handle left" data-min="0" data-max="25"></span>
<span class="highlight"></span>
<span class="handle right" data-min="-1" data-max="24"></span>
</div>
window.addEventListener("DOMContentLoaded", () => {
new dualRangeSlider(document.querySelector(".dual-range"));
});
class dualRangeSlider {
constructor(rangeElement) {
this.range = rangeElement;
this.input = rangeElement.querySelector('input');
this.min = Number(rangeElement.dataset.min);
this.max = Number(rangeElement.dataset.max);
this.handles = [...this.range.querySelectorAll(".handle")];
this.startPos = 0;
this.activeHandle;
this.handles.forEach((handle) => {
handle.addEventListener("mousedown", this.startMove.bind(this));
handle.addEventListener("touchstart", this.startMoveTouch.bind(this));
});
window.addEventListener("mouseup", this.stopMove.bind(this));
window.addEventListener("touchend", this.stopMove.bind(this));
window.addEventListener("touchcancel", this.stopMove.bind(this));
window.addEventListener("touchleave", this.stopMove.bind(this));
const rangeRect = this.range.getBoundingClientRect();
const handleRect = this.handles[0].getBoundingClientRect();
this.unitWidth = rangeRect.width / (this.max - this.min);
console.log(this.unitWidth);
this.range.style.setProperty("--x-1", handleRect.width/-2 + "px");
this.range.style.setProperty(
"--x-2",
rangeRect.width - handleRect.width / 2 + "px"
);
this.setHandleValue(this.handles[0], this.range.dataset.min);
this.setHandleValue(this.handles[1], this.range.dataset.max);
}
setHandleValue(handle, value) {
handle.dataset.value = value;
handle.dataset.label = this.formatTime(value);
if (handle.classList.contains('left')) {
this.input.dataset.from = value;
} else {
this.input.dataset.to = value;
}
}
startMoveTouch(e) {
const handleRect = e.target.getBoundingClientRect();
this.startPos = e.touches[0].clientX - handleRect.x;
this.activeHandle = e.target;
this.moveTouchListener = this.moveTouch.bind(this);
window.addEventListener("touchmove", this.moveTouchListener);
}
startMove(e) {
this.startPos = e.offsetX;
this.activeHandle = e.target;
this.moveListener = this.move.bind(this);
window.addEventListener("mousemove", this.moveListener);
}
moveTouch(e) {
this.move({ clientX: e.touches[0].clientX });
}
move(e) {
const isLeft = this.activeHandle.classList.contains("left");
const property = isLeft ? "--x-1" : "--x-2";
const parentRect = this.range.getBoundingClientRect();
const handleRect = this.activeHandle.getBoundingClientRect();
const gap = this.unitWidth;
let newX = e.clientX - parentRect.x - this.startPos;
if (isLeft) {
const otherX = parseInt(this.range.style.getPropertyValue("--x-2"));
newX = Math.min(newX, otherX - handleRect.width - gap);
newX = Math.max(newX, 0 - handleRect.width / 2);
} else {
const otherX = parseInt(this.range.style.getPropertyValue("--x-1"));
newX = Math.max(newX, otherX + handleRect.width + gap);
newX = Math.min(newX, parentRect.width - handleRect.width / 2);
}
const value = this.calcHandleValue(
(newX + handleRect.width / 2) / parentRect.width,
this.activeHandle.dataset
);
this.setHandleValue(this.activeHandle, value);
this.range.style.setProperty(property, newX + "px");
}
calcHandleValue(percentage, {min, max}) {
return Math.round(percentage * (max - min) + Number(min));
}
stopMove() {
window.removeEventListener("mousemove", this.moveListener);
window.removeEventListener("touchmove", this.moveTouchListener);
}
formatTime(hour) {
switch (parseInt(hour)) {
case 0:
case 24:
return "12 am";
case 12:
return "12 pm";
default:
if (hour < 12) {
return `${hour} am`;
} else {
return `${hour % 12} pm`;
}
}
}
}
*,*::before,*::after {
box-sizing: border-box;
font-family:helvetica;
}
body {
padding: 4em;
}
:root {
--clr-prim:#fff;
--clr-prim-h:#178242;
--clr-bad:#D91E18;
--clr-box:#e9ebee;
--clr-box-dark:#f2f2f2;
--clr-border:#17191d;
--txt-clr:#17191d;
--shadow:0px 3px 6px 0px rgba(0,0,0,0.1);
--cubic:cubic-bezier(0.11, 0.54, 0.33, 1.01);
}
.dual-range {
--range-size:32px;
--range-width:362px;
--handle-size:1.0;
height:var(--range-size);
max-width:var(--range-width);
background:var(--clr-box-dark);
border-radius:50px;
position:relative;
user-select:none;
.highlight {
position:absolute;
height:var(--range-size);
width:calc(calc(var(--x-2) - var(--x-1)) + calc(var(--range-size) * var(--handle-size)));
left:var(--x-1);
background:var(--clr-prim);
z-index:1;
border-radius:50px;
border: 1px solid var(--clr-box)
}
.handle {
width:calc(var(--range-size) * var(--handle-size));
height:calc(var(--range-size) * var(--handle-size));
background:url('https://llt.imgix.net/vt/thumb_slider.svg');
position:absolute;
border-radius:50%;
border: 1px solid var(--clr-border);
top:50%;
transform:translateY(-50%);
z-index:2;
cursor:grab;
&:active {
cursor:grabbing;
}
&.left {
left:var(--x-1);
}
&.right {
left:var(--x-2);
}
&::after {
content: attr(data-label);
font-size: 14px;
position:absolute;
bottom: calc(-.8 * var(--range-size));
left:50%;
transform:translateX(-50%);
white-space: nowrap;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment