Input range strange behavior when changing width via javascript - javascript

I have a group of sliders, they can all take a value from 0 to 100. I need the total of the group to always be less than 100 as they are percentages. I use the max attribute and some simple math to prevent the total from surpassing this.
Originally I just changed the max value, however, this doesn't change the length of the slider just what value all the way right is. This would be confusing to the user as 50% of the bar could be all the way right when it used to be in the middle.
To combat this I used a container div and change the slider's width to the corresponding percentage of the containing div.
This works as intended, but the slider handle slightly moves whenever you change a sibling slider.
Is there a way to prevent these small movements.
Is there a completely different easier way to do this seemly simple task, using only pure js.
<form id="form">
<input id="incm" type="number" value="1000"/>
<ul>
<li>Slider 0
<div class="sliderContainer"><input class="slider" id="pct0" type="range" min="0" max="100" value = "50"/></div>
<output id="pctOutput0"></output>
<output id="amtOutput0"></output>
</li>
<li>Slider 1
<div class="sliderContainer"><input class="slider" id="pct1" type="range" min="0" max="100" value = "50"/></div>
<output id="pctOutput1"></output>
<output id="amtOutput1"></output>
</li>
<li>Slider 2
<div class="sliderContainer"><input class="slider" id="pct2" type="range" min="0" max="100" value = "0"/></div>
<output id="pctOutput2"></output>
<output id="amtOutput2"></output>
</li>
<li>Slider 3
<div class="sliderContainer"><input class="slider" id="pct3" type="range" min="0" max="100" value = "0"/></div>
<output id="pctOutput3"></output>
<output id="amtOutput3"></output>
</li>
<li>Slider 4
<div class="sliderContainer"><input class="slider" id="pct4" type="range" min="0" max="100" value = "0"/></div>
<output id="pctOutput4"></output>
<output id="amtOutput4"></output>
</li>
</ul>
<p><output id="pctSum"></output><output id="amtSum"></output></p>
</form>
function calc() {
var pct = [],
pctI = [],
amt = [],
incm = document.getElementById("incm").value
for (i = 0; i < 5; i++) {
pct[i] = document.getElementById("pct" + i).value
document.getElementById("pctOutput" + i).innerHTML = pct[i] + "%"
pctI[i] = parseInt(pct[i])
amt[i] = pctI[i] * 0.01 * incm
document.getElementById("amtOutput" + i).innerHTML = "£" + amt[i]
}
var pctSum = pctI.reduce((a,b) => a + b, 0)
var amtSum = amt.reduce((a,b) => a + b, 0)
document.getElementById("pctSum").innerHTML = "Total Percentage " + pctSum + "%"
document.getElementById("amtSum").innerHTML = "Total Amount £" + amtSum
for (i = 0; i < 5; i++) {
document.getElementById("pct" + i).max = 100 - pctSum + pctI[i]
document.getElementById("pct" + i).style.width = document.getElementById("pct" + i).max + "%"
}
}
document.getElementById("form").addEventListener("input", calc)
window.onload = calc()
JSFiddle https://jsfiddle.net/Zbedjajohnson/ca5b1psy/5/

Really interesting question. Here's what I've come up with.
function initSliderGroups() {
const sliderGroups = document.querySelectorAll(".sliderGroup");
for (let sliderGroup of sliderGroups) {
const sliders = [...sliderGroup.querySelectorAll(".slider")];
const max = sliderGroup.getAttribute('max');
sliders.forEach(slider => {
slider.max = max;
slider.min = 0;
const sliderContainer = document.createElement('span');
sliderContainer.style = 'position: relative;';
slider.parentNode.insertBefore(sliderContainer, slider);
sliderContainer.appendChild(slider);
const limitDiv = document.createElement('div');
limitDiv.className = 'limitDiv';
limitDiv.style = 'position: absolute; top: 4px; left: 0; z-index: 1; width: 0; height: 5px; background-color: #555; border-radius: 5px;';
sliderContainer.appendChild(limitDiv);
slider.addEventListener("input", () => onSliderInput(slider));
});
function onSliderInput(movingSlider) {
//limit sum of values to max (defined as 100 by .sliderGroup div attribute)
const usedByOthers = sliders.filter(s => s !== movingSlider).map(s => +s.value).reduce((a, b) => a + b);
const available = max - usedByOthers;
if (movingSlider.value > available) movingSlider.value = available;
//update limit bars
for (let slider of sliders) {
const limitDiv = slider.parentNode.querySelector('.limitDiv');
const usedByOthers = sliders.filter(s => s !== slider).map(s => +s.value).reduce((a, b) => a + b);
const available = max - usedByOthers;
const sliderWidth = slider.clientWidth + 1;
const leftOffset = sliderWidth * (available / max);
const width = sliderWidth - leftOffset;
const extraOffset = 17 * (max - available) / max;
limitDiv.style.left = Math.min(leftOffset + extraOffset, sliderWidth) + 'px';
limitDiv.style.width = Math.max(width - extraOffset, 0) + 'px';
}
}
//init
if (sliders[0]) onSliderInput(sliders[0]);
}
}
initSliderGroups();
<div class="sliderGroup" max="100">
<input class="slider" type="range" value="25" /><br/>
<input class="slider" type="range" value="0" /><br/>
<input class="slider" type="range" value="0" /><br/>
<input class="slider" type="range" value="0" /><br/>
</div>
Basically you just give all your slider inputs class="slider", wrap them in an element with class="sliderGroup" max="100", then call initSliderGroups() and it'll limit those sliders to a max combined value of 100 (and shows dark grey limit bars).
In your case you'd do the following:
Add class="sliderGroup" max="100" to your <ul>.
Add my javascript above your javascript.
Delete your for loop in calc that changes max and width of sliders.

Related

Change Position of a <a> element with a <img> inside

I am running into a problem in my application where I am trying to use an range slider to determine the position of a link containing an image. Basically it grabs the value of the ranges and uses it to determine the position. However, that does not happen. Instead it does absolutely nothing. Any thoughts on why this might occur?
JS Code:
//Social Media
var rangeX = document.getElementById("rangeX");
var rangeY = document.getElementById("rangeY");
var socialChooser = document.getElementById("socialsChooser");
var inputInvite = document.getElementById("inputInvite");
var submitsocialMedia = document.getElementById("submitSocial");
var Discord = document.getElementById("discord");
var YouTube = document.getElementById("youtube");
submitsocialMedia.addEventListener("click", function () {
var socialContainer = document.getElementById("socialsContainer");
if ( socialChooser.value === "discord") {
const a = document.getElementById("discordJoin");
if (a.children.length === 0) {
const linkText = document.createElement("img");
linkText.id = "linkText";
console.log(linkText.id);
linkText.src = "/html/imgs/Discord-Logo-White.png";
a.style.marginLeft = rangeX.value + "px";
a.style.marginTop = rangeY.value + "px";
a.appendChild(linkText);
a.title = "Discord Invite Link";
a.href = `${inputInvite.value}`;
}
}
HTML Code:
<input type="range" min="1" max="1000" value="500" id="rangeX"> X
<input type="range" min="1" max="1000" value="500" id="rangeY"> Y
It's hard to test your code. So many elements referenced but not available. Instead, here's a mockup of a working range slider/element added to DOM combo. Maybe it will help you discover what's not working in your setup.
let div = document.querySelector('div');
let rangeX = document.getElementById("rangeX");
let rangeY = document.getElementById("rangeY");
document.querySelector('button').addEventListener('click', () => {
div.innerHTML += `<a href='#' style='margin-left:${rangeX.value}px; margin-top:${rangeY.value}px;'>The link</a>`;
});
div a {
display: block
}
<input type="range" min="1" max="500" value="10" id="rangeX"> X
<input type="range" min="1" max="500" value="10" id="rangeY"> Y
<button>create</button>
<div>
</div>

How to save values chosen on slider to csv using JavaScript?

The final div of my code contains 5 sliders (I show here just 2) and a “finish” button. On click, I want to be able to:
Download a CSV file with the chosen values
Display the next div (without displaying the values)
I can only use JS and HTML.
/// phase C ////
var slider1 = document.getElementById("q1");
var demo1 = document.getElementById("demo1");
var vq1 = slider1.value;
demo1.innerHTML = slider1.value;
slider1.oninput = function() {
demo1.innerHTML = this.value;
}
var slider2 = document.getElementById("q2");
var demo2 = document.getElementById("demo2");
var vq2 = slider2.value;
demo2.innerHTML = slider2.value;
slider2.oninput = function() {
demo2.innerHTML = this.value;
}
function Phase_E_action() {
document.getElementById("Phase_D").hidden = true;
document.getElementById("Phase_E").hidden = false;
var fileContent = vq1 + "," + vq2;
var bb = new Blob([fileContent], {
type: 'text/plain'
});
var a = document.createElement('a');
a.downoload = "Exp" + Date().toString().slice(4, 24) + ".csv";
a.href = window.URL.createObjectURL(bb);
a.click();
}
<div id="Phase_D">
<span class="v50_15"><h1>Please state your opinion on Bob</h1></span>
<div class="slidecontainer">
<span class="v50_16"><p>On a scale from 0 to 100, when 0 means mean and ten means nice, how would you rate Bob?</p>
<input type="range" min="1" max="100" value="50" class="slider" id="q1">
<p>Value: <span id="demo1"></span></p>
</span>
</div>
<div class="slidecontainer">
<span class="v50_17"><p>On a scale from 0 to 100, when 0 means bad and ten means good, how would you rate Bob?</p>
<input type="range" min="1" max="100" value="50" class="slider" id="q2">
<p>Value: <span id="demo2"></span></p>
</span>
</div>
</div>
you need to put variable vq1, vq2 inside callback of input event or call directly slider1.value inside Phase_E_action() function
and for download it need to append to the current page
var vq1, vq2;
var slider1 = document.getElementById("q1");
var demo1 = document.getElementById("demo1");
demo1.innerHTML = slider1.value;
slider1.oninput = function() {
// put the variable here
vq1 = demo1.innerHTML = this.value;
}
var slider2 = document.getElementById("q2");
var demo2 = document.getElementById("demo2");
demo2.innerHTML = slider2.value;
slider2.oninput = function() {
vq2 = demo2.innerHTML = this.value;
}
function Phase_E_action() {
var fileContent = vq1 + "," + vq2;
console.log(fileContent)
var bb = new Blob([fileContent], {
type: 'text/plain'
});
var a = document.createElement('a');
a.download = "Exp" + Date().toString().slice(4, 24) + ".csv";
a.href = window.URL.createObjectURL(bb);
a.innerHTML = 'fff'
document.body.appendChild(a)
a.click();
document.body.removeChild(a);
}
<div id="Phase_D">
<span class="v50_15">
<h1>Please state your opinion on Bob</h1>
</span>
<div class="slidecontainer">
<span class="v50_16">
<p>On a scale from 0 to 100, when 0 means mean and ten means nice, how would you rate Bob?</p>
<input type="range" min="1" max="100" value="50" class="slider" id="q1">
<p>Value: <span id="demo1"></span></p>
</span>
</div>
<div class="slidecontainer">
<span class="v50_17">
<p>On a scale from 0 to 100, when 0 means bad and ten means good, how would you rate Bob?</p>
<input type="range" min="1" max="100" value="50" class="slider" id="q2">
<p>Value: <span id="demo2"></span></p>
</span>
</div>
<button onclick="Phase_E_action()">
Download CSV
</button>
note: you can't use download function here because the iframe is sanboxed and allow-downloads flag is not set

How can I animate an input range to change its value dynamically?

I have an HTML element input of type range:
<body onload="rotar()">
<img src="#" id="locator" />
<input type="range" name="points" id="loop" min="1" max="20" data-show-value="true">
</body>
It goes basically like this: This page shows images that change dynamically and I want the slider with ID loop to change its value, just like the images so it moves according to them.
function rotar(){
var slider = document.getElementById("loop");
var locator = document.getElementById('locator'),
dir = 'static/visor/img/',
delayInSeconds = 3,
num = 1,
len = 20;
setInterval(function(){
locator.src = dir + num+'.png';
if (num === len){
num = 1;
}
else{
num++;
}
slider.value = num;
}, delayInSeconds * 50);}
I don't have any image dir so i just did it with an simple input. please check this http://jsfiddle.net/maxofpower/tAs6V/275/
<body onload="rotar()">
<form ><div>
<input id="loop" onchange="amount.value=rangeInput.value" oninput="amount.value=rangeInput.value" type="range" min="0" max="200" name="rangeInput" />
<input id="box" type="text" value="0" name="amount" for="rangeInput" oninput="rangeInput.value=amount.value" />
</div></form>
</body>
<script>
function rotar() {
var slider = document.getElementById("loop");
var num = 1;
setInterval(function(){
slider.value = num++;
}, 500);
};
</script>
Your issue is with the delay passed to the setInterval method, as the delay argument is in milliseconds, to get 2.8 seconds you'd have to multiply the delayInSeconds variable by 1000.
You had some mistakes in your code, you refreferenced the img#locator element with the variable name rotator the you used the variable name locator, which will cause an Undefined variable error. Im my answer I changed the variable's name from rotator to locator, also I made the delay to 2.8 seconds.
Here's a demo:
function rotar() {
var slider = document.getElementById("loop"),
locator = document.getElementById('locator'),
dir = 'static/visor/img/',
delayInSeconds = 2.8,
num = 1,
len = 20;
setInterval(function() {
locator.src = dir + num + '.png';
num = (num === len) ? 1:num + 1;
slider.value = num;
}, delayInSeconds * 1000);
}
<body onload="rotar()">
<img src="#" id="locator" />
<input type="range" name="points" id="loop" min="1" max="20" data-show-value="true">
</body>

How can I gradate between two colors using CSS/JS on a range slider?

As you can see the switch on colors is immediate rather than gradating from red to white to blue. Any suggestions?
function rangeSlider() {
//get range slider as DOM object
var rangeSlider = document.getElementById("test1").value;
//get div "container" as DOM object
var container1 = document.getElementById("container1");
if (rangeSlider < 50) {
$("#container1").css("backgroundColor", "blue");
} else {
$("#container1").css("backgroundColor", "red");
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container1" onchange="rangeSlider()">
<form action="#">
<div class="range-field">
<input type="range" id="test1" onchange="rangeSlider()" min="0" max="100" />
</div>
I added oninput for live change and changed rangeSlider
function rangeSlider() {
//get range slider as DOM object
var rangeSlider = document.getElementById("test1").value;
//get div "container" as DOM object
var container1 = document.getElementById("container1");
if (rangeSlider < 50) {
var colVal = rangeSlider*255/50;
$("#container1").css("backgroundColor", "rgb("+colVal+", "+colVal+", 255)");
} else {
var colVal = 255 - (rangeSlider-50)*255/50
$("#container1").css("backgroundColor", "rgb(255, "+colVal+", "+colVal+")");
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container1">
<form action="#">
<div class="range-field">
<input type="range" id="test1" onchange="rangeSlider()" oninput="rangeSlider()" min="0" max="100" />
</div>
you need to calculate the value for the red component and the blue component of the background color on the base of the value of the input type range.
let r,b,color;
changeColor()
test1.addEventListener("input", changeColor)
function changeColor(){
r = 255 * test1.value/100;
b = 255 * (1 - test1.value/100);
color = `rgb(${r},0,${b})`;
container1.style.background = color;
}
<div id="container1">
<form action="#">
<div class="range-field">
<input type="range" id="test1" min="0" max="100" value="0" step="1" />
</div>
You may want to consider using a background image.
function rangeSlider() {
//get range slider as DOM object
const rangeSlider = document.getElementById("test1").value;
//get div "container" as DOM object
const container1 = document.getElementById("container1");
const colorChange = 100 - rangeSlider;
$("#container1").css("background-image", "linear-gradient(to right, blue 0%, blue " + colorChange + "%, red " + colorChange + "%)");
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container1" onchange="rangeSlider()">
<form action="#">
<div class="range-field">
<input type="range" id="test1" onchange="rangeSlider()" min="0" max="100" />
</div>
</form>
</div>
One solution is working with RGBA values for your colors and calculating the alpha (opacity).
Example: 0 - 49 is blue-ish, 50 white, 51 - 100 is red-ish.
function rangeSlider() {
//get range slider as DOM object
var rangeSlider = document.getElementById("test1").value;
//get div "container" as DOM object
var container1 = document.getElementById("container1");
if (rangeSlider < 50) {
$("#container1").css("backgroundColor", "rgba(0,0,255," + ( 1 -rangeSlider / 50 ) + ")");
} else {
$("#container1").css("backgroundColor", "rgba(255,0,0," + ( rangeSlider - 50 ) / 50 + ")");
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container1" onchange="rangeSlider()">
<form action="#">
<div class="range-field">
<input type="range" id="test1" oninput="rangeSlider()" min="0" max="100" />
</div>
Are you talking about combine between two colors?
I found a good function from Sass for this https://gist.github.com/jedfoster/7939513
function colorMix (color_1, color_2, weight) {
var d2h = function(d) {return d.toString(16);} // convert a decimal value to hex
var h2d = function(h) {return parseInt(h,16);} // convert a hex value to decimal
weight = (typeof(weight) !== 'undefined') ? weight : 50; // set the weight to 50%, if that argument is omitted
var color = '#';
for(var i = 0; i <= 5; i += 2) { // loop through each of the 3 hex pairs—red, green, and blue
var v1 = h2d(color_1.substr(i, 2)), // extract the current pairs
v2 = h2d(color_2.substr(i, 2)), // combine the current pairs from each source color, according to the specified weight
val = d2h(Math.floor(v2 + (v1 - v2) * (weight / 100.0)));
while(val.length < 2) { val = '0' + val; } // prepend a '0' if val results in a single digit
color += val; // concatenate val to our new color string
}
return color;
};
function rangeSlider() {
var weight = document.getElementById('range-mix-color').value;
var resultColor = colorMix('ff0000', '0000bb', weight); // <-- BOOM!!!
document.getElementById('container').style.backgroundColor = resultColor;
}
<div id="container">
<div class="range-field">
<input type="range" id="range-mix-color" onchange="rangeSlider()" min="0" max="100">
</div>
</div>

Don't allow typing numbers between max and min value

I'm working on an Angular 4 project. I have an HTML <input type="number"> with min max and step attributes. My goal is to prevent typing numbers beyond the min-max range that is user friendly. How can I do that?
function myFunction(e) {
var min = -100;
var max = 100;
var txtValue = document.getElementById("txt").value;
var pressedValue = e.key;
var combined = parseFloat(txtValue, pressedValue);
console.log(`OLD:${txtValue}\nNEW:${pressedValue}\nCOMBINED:${combined}`);
if (combined > max) {
e.preventDefault();
console.log('ohhw snapp');
}
}
<input type="number" id="txt" value="Hello" onkeydown="myFunction(event)" min="-100" max="100" step="0.05">
My JSFiddle example
For UX reasons, I'd recommend something more like this. It may confuse the user if they just think their keyboard is not working.
$('.range-enforced').on('change', function(e){
var min=parseFloat($(this).attr('min'));
var max=parseFloat($(this).attr('max'));
var curr=parseFloat($(this).val());
if (curr > max) { $(this).val(max); var changed=true; }
if (curr < min) { $(this).val(min); var changed=true; }
if (changed) {
$warning = $(this).siblings('.warning')
$warning.text('Only ' + min + ' through ' + max + ' allowed');
$warning.show()
$warning.fadeOut(2500);
}
});
input + .warning {
background: #ffff99;
display: inline-block
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="number" class="range-enforced" min="-100" max="100" />
<div class="warning" style="display: none;"></div>
The first problem I see is you are providing 2 parameters to parseFloat which only accepts 1 (maybe you got confused with parseInt which takes 2 parameters- a string and a radix. You should combine the numbers first and do 1 parse float on the combined value.
for example
function myFunction() {
var min = -100;
var max = 100;
var inputRef = document.getElementById("txt");
var txtValue = inputRef.value;
if(isNaN(parseFloat(txtValue))){
console.log("warning input is not a number");
return;
}
var newNum = parseFloat(txtValue);
console.log(`NEW:${newNum}`);
if (newNum > max || newNum < min) {
console.log('input not in range (' + min + ", " + max + ")");
inputRef.value = "";
}
}
<input type="number" id="txt"
onkeyup="myFunction()"
min="-100"
max="100"
step="0.05"
>
The input will reset back to empty now if the input is not in range on keyup. As per the comment above you can use this to notify the user of the problem.

Categories

Resources