CodePudding user response:
let inputs = document.querySelectorAll("input[type=range]");
inputs.forEach((range) => {
range.addEventListener("input", () => {
styleRange(range);
// debugging porpuses, delete the console.logs then
console.clear();
console.log(range.value);
});
});
function styleRange(input) {
setWidth(input);
setPosition(input);
function setWidth(input) {
input.parentElement.style.setProperty(
"--before-width",
`${calcBeforeWidth(input)}px`,
);
}
function setPosition(input) {
input.parentElement.style.setProperty(
"--before-left",
`${calcPositionLeft(input)}px`,
);
}
function calcBeforeWidth(input) {
return (Math.abs(input.value) * widthPerStep(input));
}
function widthPerStep(input) {
const style = window.getComputedStyle(input);
const totalWidth = parseFloat(style.getPropertyValue("width"));
return totalWidth / numberSteps(input);
}
function numberSteps(input) {
if (input.min < input.max) return Math.abs(input.min) Math.abs(input.max);
return Math.abs(input.max) - Math.abs(input.min);
}
function calcPositionLeft(input) {
const style = window.getComputedStyle(input);
const totalWidth = parseFloat(style.getPropertyValue("width"));
const inputHeight = parseFloat(style.getPropertyValue("height"));
if (input.getAttribute("value") >= input.value)
return (totalWidth / 2) (inputHeight / 2) - calcBeforeWidth(input);
return totalWidth / 2;
}
}
body {
margin: 0;
height: 100vh;
background-color: grey;
display: grid;
place-items: center;
}
.parent-input {
--input-height: 0.5rem;
--input-color: blue;
position: relative;
display: flex;
}
input[type="range"] {
appearance: none;
height: var(--input-height);
border-radius: var(--input-height);
accent-color: var(--input-color);
width:90vw;
}
.parent-input::before {
content: "";
width: var(--before-width, 0);
height: var(--input-height, 0);
border-radius: var(--input-height);
pointer-events: none;
position: absolute;
background-color: var(--input-color);
top: 50%;
transform: translateY(-50%);
left: var(--before-left, 0);
}
<div >
<input type="range" min="-10" value="0" max="10" />
</div>
<div >
<input type="range" min="-80" value="0" max="80" />
</div>
CodePudding user response:
The following example has two <input>
. The first <input>
is rotated 180° which is the left side of zero (negative numbers) and the second <input>
is the right of zero (positive numbers). The background color from zero in either direction is blue until it reaches the current value. Figure I is the formula that determines the width of that background color:
Figure I
const per = (val - min) * 100 / (max - min);
See this article for details on custom range inputs.
Details are commented in example
// Reference <form>
const slider = document.forms.slider;
// Reference all form controls
const fc = slider.elements;
// Collect all form controls with [name="rng"]
const ranges = [...fc.rng];
// For each [name="rng"] register it to the "input" event
ranges.forEach(r => r.oninput = handleRange);
/**
Event handler passes event object by default
Get the index position of the active <input> as >idx<
Determine the index position of the other <input> as >opp<
Reference the other <input> as >sec<
Reference the closest <output> as >num<
Get the values of the active <input> [max], [min], [value] attributes
Calculate the percentage of the active <input> width in relation to
>min<, >max<, and >val< as >per<
*/
function handleRange(e) {
const idx = ranges.indexOf(this);
const opp = idx === 0 || idx % 2 === 0 ? idx 1 : idx - 1;
const sec = ranges[opp];
const num = this.closest('label').querySelector('.num');
const min = this.min;
const max = this.max;
const val = this.value;
const per = (val - min) * 100 / (max - min);
// Apply >per< to active <input> background
this.style.backgroundSize = per '% 100%';
// Display current <input> value
num.value = this.classList.contains("neg") ? -val : val;
// Toggle which <input> is active by >per<
if (per === 0) {
sec.classList.remove('off');
this.classList.add('off');
} else {
sec.classList.add('off');
this.classList.remove('off');
}
sec.value = 0;
sec.style.backgroundSize = '0% 100%';
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
}
html {
font: 300 2ch/1 Consolas;
}
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-image: radial-gradient(rgb(237, 221, 255), rgba(195, 145, 243, 1));
}
#slider {
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: center;
width: 30rem;
}
label {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 12px;
}
input,
output {
display: inline-block;
vertical-align: middle;
font: inherit;
}
.num {
width: 3rem;
padding: 5px;
border-radius: 3px;
text-align: center;
color: #fff;
background: #0045ff;
box-shadow: 0 0 2px 0 #555;
}
.rng {
-webkit-appearance: none;
position: relative;
z-index: 1;
width: 10rem;
height: .65rem;
border-radius: 1rem;
background-color: rgba(255, 255, 255, 0.6);
background-image: linear-gradient(#0045ff, #0045ff);
background-size: 0% 100%;
background-position: 0.625rem 0;
background-repeat: no-repeat;
}
.rng:focus {
outline: none;
}
.rng::-webkit-slider-thumb {
-webkit-appearance: none;
height: 1.25rem;
width: 1.25rem;
border-radius: 1rem;
background: #0045ff;
box-shadow: 0 0 2px 0 #555;
transition: background .3s ease-in-out;
cursor: ew-resize;
}
.rng::-moz-range-thumb {
-webkit-appearance: none;
height: 1.25rem;
width: 1.25rem;
border: none;
border-radius: 1rem;
background: #0045ff;
box-shadow: 0 0 2px 0 #555;
transition: background .3s ease-in-out;
cursor: ew-resize;
}
.rng:hover::-webkit-slider-thumb {
background: #0002ff;
}
.rng:hover::-moz-range-thumb {
background: #0002ff;
}
.rng:focus::-webkit-slider-thumb {
outline: 3px solid #0045ff;
outline-offset: 0.125rem;
}
.rng:focus::-moz-range-thumb {
outline: 3px solid #0045ff;
outline-offset: 0.125rem;
}
.rng.off {
z-index: 0;
}
.rng.off::-webkit-slider-thumb {
opacity: 0;
}
.rng.off::-moz-range-thumb {
opacity: 0;
}
.rng::-webkit-slider-runnable-track {
-webkit-appearance: none;
border: none;
background: transparent;
box-shadow: none;
}
.rng::-moz-range-track {
-webkit-appearance: none;
border: none;
background: transparent;
box-shadow: none;
}
.neg {
transform: rotate(180deg) translateX(-0.625rem);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.pos {
transform: translateX(-0.625rem);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
<form id="slider">
<label>
<input name="rng" type="range" min="0" max="10" value="10">
<input name="rng" type="range" min="0" max="10" value="0">
<output name="num" >0</output>
</label>
<label>
<input name="rng" type="range" min="0" max="100" value="100">
<input name="rng" type="range" min="0" max="100" value="0">
<output name="num" >0</output>
</label>
</form>