Working with a set of range sliders. Fix my jsfiddle? - javascript

I need my three range sliders to share from a common "value pool." So if the max is 100, and slider-1 is set to 51. you should expect slider-2 and 3 to take from a shared pool of 49. The way I have it now, if you change slider 1, slider 2 and 3 are equal. This is not ideal!
How would you go about making it so I could set slider-1 to a value of "51" for example,
and then set slider-2 to a value of "30" making slider-3 automatically "19".
And then from there, if I bump slider 3 with a value of "19" up to "20" for example, slider-2 changes to "29.5" and slider-1 changes to "50.5"
Here is a jsfiddle, hope my question makes sense:
https://jsfiddle.net/7ho3zfve/

There are a few things to note: first, you are really using redundant code. If you decide later to add more inputs, you're doing the same thing multiple times. Instead, use classes. You can have a single function or collection of functions that work on all inputs, as the behavior is going to be the same across them all.
Now, if I understand you correctly, you want a set max value to be respected, and any elements that have NOT yet been manually moved, to have their values set to an equal portion of the remaining max value? The code below will do that. It includes a draggable detector (basically, the sliders have a mousedown event that sets the isDragging, and the mousemove event checks that it is, in fact, dragging), which is when the remainder of the maxVal is split. The code is pretty heavily commented, it should get you most of the way to what you're looking for.
The one thing I haven't really worked on yet, and potentially a big pitfall, is that, when two of the three sliders have been manually set, you can still move the THIRD slider to the right. I'm guessing you'd want to catch that, but I haven't worked out that logic yet.
var maxVal = 1000,
isDragging = false,
fixedEls, flexEls;
/***
* Tracking the dragging -- on mousedown, I set the isDragging
* to true, then I check that for the mousemove on this element.
* On mouseup, I reset the isDragging to false, thus ending the
* simulated drag event.
***/
$(".slide-control").on("mousemove", function() {
if (isDragging) {
// I want to keep two collections of sibling elements:
// those that have NOT been moved, and those that HAVE.
// I simply add the .slider-fixed to those that have,
// which are elements I don't want to be moveable later.
fixedEls = $(this).siblings("input.slider-fixed");
flexEls = $(this).siblings("input").not(".slider-fixed");
// The subtotal I will be using is simply the current
// slider element plus any elements that have already had
// their .slider-fixed class set. The resulting value,
// subtracted from the above maxVal, will be the pool of
// values for any non-fixed sliders.
var subtotal = parseInt($(this).val());
fixedEls.each(function() {
subtotal += parseInt($(this).val());
});
// The sharedVals is the remaining value, evenly split
// between all moveable elements.
var sharedVals = (maxVal - subtotal) / flexEls.length;
// Set the element's val to that sharedVal.
flexEls.each(function() {
$(this).val(sharedVals)
})
/***
* The following lines simply output the values of each slider
* to the results pane. Not a necessity, more a debug thing.
***/
$(".results").empty();
$(this).parent().children("input").each(function() {
var myHtml = "<p>Slider " + $(this).prop("id") + ": " + $(this).val() + "</p>"
$(".results").append(myHtml);
})
}
}).on("mouseup", function() {
// When the drag event has ended, set the toggle off.
isDragging = false;
// I don't want this particular slider affected by any other
// movements.
$(this).addClass("slider-fixed");
}).on("mousedown", function() {
isDragging = true;
});
.slide-control {
display: block;
margin-left: 20px;
}
.slider-fixed::before {
margin-left: -20px;
content: ">>";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="header">
<h3>A slider control</h3>
<h4>Note: >> beside a slider indicates that it won't be affected by any further movement of other sliders.</h4>
</div>
<input type="range" id="myRange" value="0" min="0" max="1000" class="slide-control">
<input type="range" id="myRange2" value="0" min="0" max="1000" class="slide-control">
<input type="range" id="myRange3" value="0" min="0" max="1000" class="slide-control">
<div class="results">
</div>

Went with CumminUp07's library suggestion here:keith-wood.name/linkedsliders.html to accomplish this.
Thanks everyone for putting together some useful information and code on this thread.

Related

Input range move only by one step

I'm trying to use input range moving one by one. For exemple here is my slider :
<input id="replay-input" type="range" min="0" max="100" step="1" value="0">
Really basic. But the problem is that you can freely move from 5 to 89 by one click. What I would like to achieve is to increment or decrements only by one so my value will look like this is if I move the cursor :
0 -> 1 -> 2 -> 3 -> 4
Then if I go back :
4 -> 3 -> 2 -> 1 -> 0
With big max like 5000 we have missing steps because there is too many values on a small area :
4800 -> 4799 -> 4790 -> 4785
Is there a way to avoid this ? One idea may be to use JS and store value and only allow change of one but maybe there is something more simple in HTML ?
https://jsfiddle.net/vcoyxg4v/5/
Thanks !
I would not suggest limiting the default functionality of the slider control, as people expect it to work in a certain way: think also of dragging the slider, using the arrow/page/home/end keys when the control has focus, ...
But for the sake of the exercise, here is code that will make the slider value only increase/decrease with 1. So if you drag, the value will slowly catch up with the mouse position. This solution uses a data property to keep track of the previous value:
$('#replay-input').data({
was: $('#replay-input').val()
}).on('input', function () {
var data = $(this).data();
$(this).data({
was: this.value = +data.was + (this.value > data.was || -1)
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="replay-input" type="range" min="0" max="100" step="1" value="0">
Using animation
As an extension to this idea, you could use the animate method to take care of the progression towards the target value:
$('#replay-input').data({
current: $('#replay-input').val()
}).on('input', function () {
var data = $(this).data(),
value = this.value;
$(this).data(data = {
current: this.value = +data.current, // restore previous value
}).stop().animate({ value }, { // ... and animate towards the target
duration: 500, // Experiment with other durations...
easing: 'swing', // Experiment with other easing values...
step: function(val) {
$(this).data({current: this.value}) // accept change
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="replay-input" type="range" min="0" max="100" step="1" value="0">
As indicated in the comments, you can experiment a bit with the animation options duration and ease to tailor it to your needs.
There is a slight visual glitch just after a click because the slider also receives an input event on mouseup.

Callback after element moved

I have list that scrolls up using velocity. I want to play sound each time, first visible item of the list scrolled up.
<div class="viewport" data-winner="">
<ul class="participants-holder container" id="ph">
<li>...<li> //many li elements
</ul>
</div>
moveToEl(name) {
...
$(container).velocity({
translateY: -(offsetToScroll)+'px'
}, {
duration: 15000,
easing: [.74,0,.26,1],
complete: (el) => {
...
// complete animation callback
},
progress: (els, complete, remaining, start, tweenVal) => {
console.log(Math.floor(complete * 100) + '%')
// I think some check should do during progress animation
}
})
}
How to handle event or track changes when each element or entire list are scrolled up by certain pixels, for instance 62px. How can I detect this and call callback function on this happened.
You can find the current TranslateY using something like
+this.container.style.transform.replace(/[^0-9.]/g, '');
from https://stackoverflow.com/a/42267490/1544886, and compare it to the previous value plus an offset.
In the Roulette class add this.prevTranslatePos = 0.0; for storing the old value.
progress: (els, complete, remaining, start) => {
// from https://stackoverflow.com/a/42267490/1544886
var translatePos = +this.container.style.transform.replace(/[^0-9.]/g, '');
if (translatePos >= (this.prevTranslatePos + 62))
{
//console.log(translatePos, this.prevTranslatePos);
this.prevTranslatePos = translatePos;
this.sound.pause();
this.sound.currentTime = 0;
this.sound.play();
}
}
Demo applied to the 'Go To' button only: http://codepen.io/anon/pen/yMXwgd?editors=1010
Note that the sound cuts out when it runs too quickly, but that could be handled a few different ways.
Add a scroll eventListener to the parent element of the list (I believe it's participants-holder in your case), and within that do a check for whether the right amount of pixels have moved since the last check. Store the current position, and compare it to the last time you moved the desired amount.
Hope that helps!

jQuery Knob partially read only

I have a collection of jQuery knobs on my page that represent percentages. Each knob can have a value from 0-100, however, each subsequent knob should not have a value less than the previous knob. Essentially I want to make part of the knob read only - to prevent the user from dragging the value below the previous knob's value - similar to a 'min' value.
Example
Knob 1 25%
Knob 2 50% (min value still 0, max 100, but the value has to be greater than 25)
Knob 3 75%
Knob 4 100%
I have tried binding to the 'change' event on the knob, but that is not giving me what I want. Below I'm binding to the change event to try to limit the value - this is hard coded to test the simple use case. I am probably going to want to bind to the 'draw' event to limit the values on animation but haven't got that far.
$(this).trigger('configure', {
'change': function (v) {
console.log("Updating value " + v);
if(v < 25) {
console.log("Updating value");
this.cv = 25;
}
}
});
Not sure if this is the best way to do it, but you can manipulate the angleOffset and angleArc options of the dial based on the value of the previous one. For example, if you have two dials:
HTML
<input type="text" value="75" id="dial1">
<input type="text" value="75" id="dial2">
Your javascript would be something like this:
$(function () {
$("#dial1").knob({
'change': function (v) {
$('#dial2')
.trigger(
'configure', {
'angleOffset': (v / 100 * 360),
'angleArc': ((100-v) / 100 * 360),
'min': v,
});
}
});
$("#dial2").knob();
});
As for the empty space left in the dial, you can fix that by setting the canvas background to an image of a full knob.
CSS
canvas {
background: url('http://imgur.com/aeTBhL9.png');
}
Here's a jsFiddle example.

jQuery Mobile slider snap?

I've spent the better past of 2 hours trying out random code from different sources that claim they've managed to snap the slider to particular points. I'm not sure if this is feasible with the jQuery Mobile library, since the jQuery UI version does support it, but i'm really needing to figure something out for a current project.
Essentially I have a timeline with different dates. They're not in "steps", like 10's or 20's, they're different numbers. Something like 1, 3, 10, 12, 32. I need the slider to snap at those points.
Currently, I have something that's testing the step attr only.
$('#slider-1').live( "change", function(e) {
var step = $(this).attr('step'),
val = $(this).val();
if(step % val == step || val == step){
console.log(val)
}
});
The issue i'm having with the slider, not only the snapping, is when i slide anywhere near 20 or 40 or 60 etc, i'm getting console logs. It's not logging ON 20 etc.
Figured it out after many iterations of code and reading the documentation for specific events I can bind.
So essentially I had a timeline of numbers in my array that looks something like:
-0--10--20-----70--100---150---200---250---300---350---400-----500----------------1000
I needed to snap to the nearest number depending on the position of the slider-handle. I did this by testing the current value when the slider was being moved against the closest number in the array. I would then dynamically change the sliders value and would then have to do a force refresh of the slider to see the change.
$(function() {
/*
Array of values between 0 - 1000 (can be any set of numbers you want to snap to,
but you must make sure the 'max' attribute on the input element is <= the last
value in the array)
*/
var values = [0, 10, 20, 70, 100, 150, 200, 250, 300, 350, 400, 500, 1000],
/*
A way for us to check the current and previous values of the slider so we only
call doSomething() if these are different. This way the function isn't called twice. These should NOT have the same values on init.
*/
currentVal = 0,
prevVal = null,
// Assign our slider to a variable so we're not hitting the dom repeatedly.
$slider = $("#slider-1");
/*
After each change of our input value, we assign the slider value to the nearest number
in our array for our "snap" effect
*/
$($slider).bind("change", function(){
$($slider).val(findNearest($($slider).val()))
});
/*
We must capture click events on the slider itself if the user doesn't drag the handle.
*/
$(".ui-slider[role=application]").bind("vclick",function(){
$($slider).slider('refresh');
})
/*
jQuery creates new dom elements for us, which in the case of the handle, is an anchor element.
Use mouseup method to test on the computer, otherwise touchend method for mobile devices.
*/
$("a.ui-slider-handle").touchend(function(){
// If our currentVal hasn't changed after a snap action, do nothing, otherwise call doSomething()
if(currentVal != prevVal) {
prevVal = currentVal;
doSomething();
}
// Force slider refresh for visible snap effect
$($slider).slider('refresh');
})
// Used to iterate over our array and check for nearest value to what is passed in
function findNearest(goal) {
var closest = null;
$.each(values, function(){
if (closest == null || Math.abs(this - goal) < Math.abs(closest - goal)) {
closest = this;
}
});
return currentVal = Number(closest);
}
function doSomething(){
...
}
});
And my HTML:
<label for="slider-1" id="slider_label_1">Input slider:</label>
<input type="range" name="slider-1" id="slider-1" value="0" min="0" max="1000" step="10" />

A jQuery/javascript auto-sizable grid control

I am implementing a jquery file upload page. While a user is adding files, I want them to be getting listed in a form of an icons in an auto-sizable grid.
Auto-sizable means it provides maximum space for containing elements. When there is two objects - it woud look like (I know i will have to handle image resizing myself):
When several are added:
Is there a "grid control" (jquery perhaps) that does at least close to what I need sizing wise?
First of all, please keep in mind that I'm a jQuery newbie and this is my first post on Stackoverflow :)
I've the same problem and I've try to fix it using jQuery and CSS. This is my body tag content:
<div id="controls">
Controls:
<button id="add">+</button>
<button id="del">-</button>
Current ratio:
<span id="value"></span>
<button id="increase">+</button>
<button id="decrease">-</button>
Referred to:
<form style="display: inline;">
width<input type="radio" name="maximize" value="width">
height<input type="radio" name="maximize" value="height">
</form>
</div>
<div id="elements" style="width: 500px; height: 300px; background: black;">
</div>
<script>
ratio = 1;
ratioWidth = true;
function autoresize() {
boxes = $('#elements').children().size();
rows = Math.ceil(Math.sqrt(boxes/ratio));
columns = Math.ceil(boxes/rows);
if (!ratioWidth) {
tmp = rows;
rows = columns;
columns = tmp;
}
$('#elements').children().css('width', 100/rows+'%');
$('#elements').children().css('height', 100/columns+'%');
}
function add() {
red = Math.floor(Math.random()*256);
green = Math.floor(Math.random()*256);
blue = Math.floor(Math.random()*256);
color = 'rgb('+red+','+green+','+blue+')';
$('#elements').append("<div style=\"background: "+color+"; float: left;\"></div>");
autoresize();
}
function update() {
$('#value').text(ratio);
autoresize();
}
function remove() {
$('#elements').children().last().remove();
update();
}
function increase() {
ratio++;
update();
}
function decrease() {
if (ratio > 1) {
ratio--;
update();
}
}
$(document).ready(function() {
$('#add').click(add);
$('#del').click(remove);
$('#increase').click(increase);
$('#decrease').click(decrease);
if (ratioWidth) value = 'width'
else value = 'height'
$('input[type=radio]').filter('[value='+value+']').attr('checked', true);
$('input[type=radio]').live('change', function() {
if ($(this).val() == 'width') ratioWidth = true
else ratioWidth = false;
autoresize();
});
update();
//$(window).bind("resize", autoresize);
});
</script>
You could remove the background color stuff and put your icons centered in those boxes.
If you find a better way of if you improve this code, please let me know :)
Edit 1 - I've added some Math.floor(...) to remove a bug when boxes side has repeating decilmals: very size is a simple integer. Now dimensions are fetched from the container div, I use black as background color for the main container and I've noticed a little issue: sometimes I see a black border near the little boxes, even if I don't set any background color. Could it be a Firefox rendering glitch?
Edit 2 - Now it's possible to set if you prefer to auto-expand horizontally, vertically, or none of them. I've tried to write a better code, and I've commented autoresize when the window is resized (use it only if your container box hasn't a fixed height/width). I think that now it needs an ratio option, in order to specify if width have to be twice longer for example. Live example: http://experimental.frafra.eu/maxarea2.html
Edit 3 - New code! Better graphical representation and a new parameter: ratio. Ratio is a coefficient between the ratio between main container width/height and the elements one. Live example: http://experimental.frafra.eu/maxarea3.html

Categories

Resources