Reputation: 954
I have a problem. I created the a slider with 2 dots, for a from and to like this:
[slider] {
width: 300px;
position: relative;
height: 5px;
margin: 45px 0 10px 0;
}
[slider] > div {
position: absolute;
left: 13px;
right: 15px;
height: 5px;
}
[slider] > div > [inverse-left] {
position: absolute;
left: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [inverse-right] {
position: absolute;
right: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [range] {
position: absolute;
left: 0;
height: 5px;
border-radius: 14px;
background-color: #d02128;
}
[slider] > div > [thumb] {
position: absolute;
top: -7px;
z-index: 2;
height: 20px;
width: 20px;
text-align: left;
margin-left: -11px;
cursor: pointer;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);
background-color: #FFF;
border-radius: 50%;
outline: none;
}
[slider] > input[type=range] {
position: absolute;
pointer-events: none;
-webkit-appearance: none;
z-index: 3;
height: 14px;
top: -2px;
width: 100%;
opacity: 0;
}
div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
border: transparent;
}
div[slider] > input[type=range]:focus {
outline: none;
}
div[slider] > input[type=range]::-webkit-slider-thumb {
pointer-events: all;
width: 28px;
height: 28px;
border-radius: 0px;
border: 0 none;
background: red;
-webkit-appearance: none;
}
div[slider] > input[type=range]::-ms-fill-lower {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-fill-upper {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-tooltip {
display: none;
}
[slider] > div > [sign] {
opacity: 0;
position: absolute;
margin-left: -14px;
top: -39px;
z-index:3;
background-color: #d02128;
color: #fff;
width: 28px;
height: 28px;
border-radius: 28px;
-webkit-border-radius: 28px;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;
}
[slider] > div > [sign]:after {
position: absolute;
content: '';
left: 0;
border-radius: 16px;
top: 19px;
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top-width: 16px;
border-top-style: solid;
border-top-color: #d02128;
}
[slider] > div > [sign] > span {
font-size: 12px;
font-weight: 700;
line-height: 28px;
}
[slider]:hover > div > [sign] {
opacity: 1;
}
<div slider id="slider-distance">
<div>
<div inverse-left style="width:70%;"></div>
<div inverse-right style="width:70%;"></div>
<div range style="left:0%;right:0%;"></div>
<span thumb style="left:0%;"></span>
<span thumb style="left:100%;"></span>
<div sign style="left:0%;">
<span id="value">0</span>
</div>
<div sign style="left:100%;">
<span id="value">100</span>
</div>
</div>
<input type="range" value="0" max="100" min="0" step="1" oninput="
this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[1].style.width=value+'%';
children[5].style.left=value+'%';
children[7].style.left=value+'%';children[11].style.left=value+'%';
children[11].childNodes[1].innerHTML=this.value;" />
<input type="range" value="100" max="100" min="0" step="1" oninput="
this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[3].style.width=(100-value)+'%';
children[5].style.right=(100-value)+'%';
children[9].style.left=value+'%';children[13].style.left=value+'%';
children[13].childNodes[1].innerHTML=this.value;" />
</div>
Now, I want 3 of those sliders, so I wanted to re-use the javascript in the input. To do that I added 2 functions: updateSlider1 and updateSlider2. In the input I just call the function for each slider, but when I run my code, nothing happens. Here is the code that doesn't work:
function updateSlider1() {
this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[1].style.width=value+'%';
children[5].style.left=value+'%';
children[7].style.left=value+'%';children[11].style.left=value+'%';
children[11].childNodes[1].innerHTML=this.value;
}
function updateSlider2() {
this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));
let value = (this.value/parseInt(this.max))*100
var children = this.parentNode.childNodes[1].childNodes;
children[3].style.width=(100-value)+'%';
children[5].style.right=(100-value)+'%';
children[9].style.left=value+'%';children[13].style.left=value+'%';
children[13].childNodes[1].innerHTML=this.value;
}
[slider] {
width: 300px;
position: relative;
height: 5px;
margin: 45px 0 10px 0;
}
[slider] > div {
position: absolute;
left: 13px;
right: 15px;
height: 5px;
}
[slider] > div > [inverse-left] {
position: absolute;
left: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [inverse-right] {
position: absolute;
right: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
}
[slider] > div > [range] {
position: absolute;
left: 0;
height: 5px;
border-radius: 14px;
background-color: #d02128;
}
[slider] > div > [thumb] {
position: absolute;
top: -7px;
z-index: 2;
height: 20px;
width: 20px;
text-align: left;
margin-left: -11px;
cursor: pointer;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);
background-color: #FFF;
border-radius: 50%;
outline: none;
}
[slider] > input[type=range] {
position: absolute;
pointer-events: none;
-webkit-appearance: none;
z-index: 3;
height: 14px;
top: -2px;
width: 100%;
opacity: 0;
}
div[slider] > input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
border: transparent;
}
div[slider] > input[type=range]:focus {
outline: none;
}
div[slider] > input[type=range]::-webkit-slider-thumb {
pointer-events: all;
width: 28px;
height: 28px;
border-radius: 0px;
border: 0 none;
background: red;
-webkit-appearance: none;
}
div[slider] > input[type=range]::-ms-fill-lower {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-fill-upper {
background: transparent;
border: 0 none;
}
div[slider] > input[type=range]::-ms-tooltip {
display: none;
}
[slider] > div > [sign] {
opacity: 0;
position: absolute;
margin-left: -14px;
top: -39px;
z-index:3;
background-color: #d02128;
color: #fff;
width: 28px;
height: 28px;
border-radius: 28px;
-webkit-border-radius: 28px;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;
}
[slider] > div > [sign]:after {
position: absolute;
content: '';
left: 0;
border-radius: 16px;
top: 19px;
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top-width: 16px;
border-top-style: solid;
border-top-color: #d02128;
}
[slider] > div > [sign] > span {
font-size: 12px;
font-weight: 700;
line-height: 28px;
}
[slider]:hover > div > [sign] {
opacity: 1;
}
<div slider id="slider-distance">
<div>
<div inverse-left style="width:70%;"></div>
<div inverse-right style="width:70%;"></div>
<div range style="left:0%;right:0%;"></div>
<span thumb style="left:0%;"></span>
<span thumb style="left:100%;"></span>
<div sign style="left:0%;">
<span id="value">0</span>
</div>
<div sign style="left:100%;">
<span id="value">100</span>
</div>
</div>
<input type="range" value="0" max="100" min="0" step="1" oninput="updateSlider1" />
<input type="range" value="100" max="100" min="0" step="1" oninput="updateSlider2" />
</div>
What am I doing wrong and how can I fix it?
Upvotes: 1
Views: 221
Reputation: 65806
You shouldn't be using inline event attributes like oninput
in 2020. That's a 25+ year old technique that just won't die because people keep copying other people's code when they see it.
By removing the inline JavaScript from the HTML and creating named functions, you can reuse that code for any/all sliders you decide to make.
Next, your HTML is not valid because you aren't allowed to just make up attributes and stick them in elements, like this:
<div slider id="slider-distance">
Instead, add a class to the element and forget about unique IDs, like this:
<div class="slider">
Now, in moving slider
from being an attribute to a class, your CSS will need to be updated as well, so this:
[slider] {
width: 300px;
position: relative;
height: 5px;
margin: 45px 0 10px 0;
}
would need to become this:
.slider {
width: 300px;
position: relative;
height: 5px;
margin: 45px 0 10px 0;
}
...And, when possible, avoid inline styles as well. In the example below, I have moved most of those into the CSS classes.
In the example below, there are 3 sliders that all reuse the same JavaScript for the handles. I have updated the CSS as well.
You can see that now you can have any amount of sliders without having to change any of the CSS or JavaScript - - just drop in another chunk of HTML and it works.
// Don't use inline HTML event attributes like "oninput"
// Separate your JavaScript from your HTML
// Find all the sliders and loop over them
document.querySelectorAll(".slider").forEach(function(slider){
// Set event handlers for the left and right handles of each slider
slider.querySelector("input.left").addEventListener("input", left);
slider.querySelector("input.right").addEventListener("input", right);
});
function left(){
this.value=Math.min(this.value,this.parentNode.childNodes[5].value-1);
let value = (this.value/parseInt(this.max))*100;
// Don't use childNodes and children with indexes as they use live node
// lists that aren't efficient and difficult to maintain. Instead, just locate the
// element you want with .closest, .querySelector and its class, which is
// much easier to read and understand.
this.closest(".slider").querySelector(".inverse-left").style.width = value + '%';
this.closest(".slider").querySelector("div.range").style.left = value + '%';
this.closest(".slider").querySelector("span.thumb").style.left = value + '%';
this.closest(".slider").querySelector(".sign").style.left = value + '%';
this.closest(".slider").querySelector(".sign > span").textContent=this.value;
}
function right(){
this.value=Math.max(this.value,this.parentNode.childNodes[3].value-(-1));
let value = (this.value/parseInt(this.max))*100;
this.closest(".slider").querySelector(".inverse-right").style.width = (100-value) + '%';
this.closest(".slider").querySelector("div.range").style.right = (100-value) + '%';
this.closest(".slider").querySelector("div > span+span").style.left = value + '%';
this.closest(".slider").querySelector(".sign+.sign").style.left = value + '%';
this.closest(".slider").querySelector(".sign+.sign > span").textContent = this.value;
}
.slider {
width: 300px;
position: relative;
height: 5px;
margin: 45px 0 80px 0;
}
.slider > div {
position: absolute;
left: 13px;
right: 15px;
height: 5px;
}
.slider > div > .inverse-left {
position: absolute;
left: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
width:70%;
}
.slider > div > .inverse-right {
position: absolute;
right: 0;
height: 5px;
border-radius: 10px;
background-color: #CCC;
margin: 0 7px;
width:70%;
}
.slider > div > .range {
position: absolute;
left: 0;
right:0;
height: 5px;
border-radius: 14px;
background-color: #d02128;
}
.slider > div > .thumb {
position: absolute;
top: -7px;
z-index: 2;
height: 20px;
width: 20px;
text-align: left;
margin-left: -11px;
cursor: pointer;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4);
background-color: #FFF;
border-radius: 50%;
outline: none;
}
.slider > div > .thumb.left { left:0; }
.slider > div > .thumb.right { left:100%; }
.slider > input[type=range] {
position: absolute;
pointer-events: none;
-webkit-appearance: none;
z-index: 3;
height: 14px;
top: -2px;
width: 100%;
opacity: 0;
}
div.slider > input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
border: transparent;
}
div.slider > input[type=range]:focus {
outline: none;
}
div.slider > input[type=range]::-webkit-slider-thumb {
pointer-events: all;
width: 28px;
height: 28px;
border-radius: 0px;
border: 0 none;
background: red;
-webkit-appearance: none;
}
div.slider > input[type=range]::-ms-fill-lower {
background: transparent;
border: 0 none;
}
div.slider > input[type=range]::-ms-fill-upper {
background: transparent;
border: 0 none;
}
div.slider > input[type=range]::-ms-tooltip {
display: none;
}
.slider > div > .sign {
opacity: 0;
position: absolute;
margin-left: -14px;
top: -39px;
z-index:3;
background-color: #d02128;
color: #fff;
width: 28px;
height: 28px;
border-radius: 28px;
-webkit-border-radius: 28px;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
text-align: center;
}
.slider > div > .sign:after {
position: absolute;
content: '';
left: 0;
border-radius: 16px;
top: 19px;
border-left: 14px solid transparent;
border-right: 14px solid transparent;
border-top-width: 16px;
border-top-style: solid;
border-top-color: #d02128;
}
.slider > div > .sign > span {
font-size: 12px;
font-weight: 700;
line-height: 28px;
}
.slider:hover > div > .sign {
opacity: 1;
}
<div class="slider">
<div>
<div class="inverse-left"></div>
<div class="inverse-right"></div>
<div class="range"></div>
<span class="thumb left"></span>
<span class="thumb right"></span>
<div class="sign" style="left:0%;">
<span>0</span>
</div>
<div class="sign" style="left:100%;">
<span>100</span>
</div>
</div>
<input type="range" value="0" max="100" min="0" step="1" class="left">
<input type="range" value="100" max="100" min="0" step="1" class="right">
</div>
<div class="slider">
<div>
<div class="inverse-left"></div>
<div class="inverse-right"></div>
<div class="range"></div>
<span class="thumb left"></span>
<span class="thumb right"></span>
<div class="sign" style="left:0%;">
<span>0</span>
</div>
<div class="sign" style="left:100%;">
<span id="value">100</span>
</div>
</div>
<input type="range" value="0" max="100" min="0" step="1" class="left">
<input type="range" value="100" max="100" min="0" step="1" class="right">
</div>
<div class="slider">
<div>
<div class="inverse-left"></div>
<div class="inverse-right"></div>
<div class="range"></div>
<span class="thumb left"></span>
<span class="thumb right"></span>
<div class="sign" style="left:0%;">
<span>0</span>
</div>
<div class="sign" style="left:100%;">
<span id="value">100</span>
</div>
</div>
<input type="range" value="0" max="100" min="0" step="1" class="left">
<input type="range" value="100" max="100" min="0" step="1" class="right">
</div>
Upvotes: 4