I have this code here:
function slideDown(){
//get the element to slide
var sliding = document.getElementById('slideDiv'+x);
//add 1px to the height each time
sliding.style.height = parseInt(sliding.style.height)+5+'px';
t=setTimeout(slideDown,15);
if (sliding.style.height == "401px"){clearTimeout(t);}
}
which gets element slideDivx and increases its height until it reaches 401px.
I have 20 of these divs (each called slideDiv1, slideDiv2, ...) and what I want is for div 10 to start, then when it has reached about 100px in height, I want 9 and 11 to start and so on so forth.
I've tried setInterval and increased x each time, but that doesn't work because it stops the previous div animating and moves on to the next one before it's finished. I think that's because I'm changing that function right?
What I'm wondering is, do I have to duplicate the function for each set of divs or can I automate that?
I have to be able to do it all in native JavaScript rather than jQuery because it's for a university project.
It sounds like you simply need to create slideDown with a parameter and then send your set of elements in an array like so:
var elementIntervals = [];
function animateElements(elementArray)
{
for(var j = 0; j < elementArray; j++)
{
(function(element, index)
{
elementIntervals[index] = setTimeout(function()
{
slideDown(element, index);
}, 15 + (100 * index));
})(elementArray[j], j);
}
}
function slideDown(sliding, index)
{
sliding.style.height = parseInt(sliding.style.height)+5+'px';
if(sliding.style.height == "401px")
clearTimeout(elementIntervals[index]);
else
elementIntervals[index] = setTimeout(function()
{
slideDown(sliding, index);
}, 15);
}
Related
I am attempting to add next/prev and then swipe events to a bootstrap modal image gallery. The thumbnails are displayed in a grid so when I try closest(), siblings(), and next() I am only able to go to the images below and above in the same column and not the other columns in the grid.
Each thumbnail has an id with the images ID in the database. ie:
<a class="image-gallery-item" id="img-1002" href="#showImageModal" data-toggle="modal" onclick="..."><img src="..." /></a>
<a class="image-gallery-item" id="img-4664" href="#showImageModal" data-toggle="modal" onclick="..."><img src="..." /></a>
Every link passes the images src, caption, and ID to a function which changes out the info displayed the modal. All that works fine, I just can't seem to get next/prev working when trying to trigger the next item.
Ideally, instead of using next() and closest() and sibling() methods, I would LIKE to just target the ID greater than the current id for first next and lesser than the current ID for the first previous and trigger a click if the element exists.
Thanks.
UPDATE
Per #Ersian's suggestion, I created an array and cycle thru the images by index. They're still going by column but at least they're moving to the next column after. This will present a little confusion for users, I feel, who will expect the images to display left to right before going to the next row of square thumbnails.
$('.image-gallery-item').modal('hide');
setTimeout(function () {
var thisIMG = $("#imgid").html();
var imgs = document.getElementsByClassName("image-gallery-item");
var $imgvalues = Array.prototype.map.call(imgs, function (el) {
return el.id;
});
index = $imgvalues.indexOf(thisIMG);
if (index >= 0 && index < $imgvalues.length - 1)
nextItem = $imgvalues[index + 1];
$('#' + nextItem).trigger('click');
}, 1000);
UPDATE - WORKING CODE
Found the solution by adding sort to the array and then looped the next/prev if hitting the end or start of items with the class image-gallery-item. I'll post the working code as an answer in case anyone else finds this question and wants to make a bootstrap modal image gallery with gridalicious, isotope, or masonry layouts with columns.
Keep in mind you will need to add buttons with show-next-image and show-previous-image classes to your modal. After that, the following code will let you cycle thru images with the class image-gallery-item.
$('#show-next-image, #show-previous-image').click(function () {
if ($(this).attr('id') == 'show-previous-image') {
$('#showImageModal').modal('hide');
setTimeout(function () {
var thisIMG = $("#imgid").html();
var imgs = document.getElementsByClassName("image-gallery-item");
var $imgvalues = Array.prototype.map.call(imgs, function (el) {
return el.id;
});
var lastIMG = $('.image-gallery-item').length;
$imgvalues.sort();
index = $imgvalues.indexOf(thisIMG);
if (index <= lastIMG - 2) {
nextItem = $imgvalues[index + 1];
} else {
nextItem = $imgvalues[0];
}
$('#' + nextItem).trigger('click');
}, 1000);
} else {
$('#showImageModal').modal('hide');
setTimeout(function () {
var thisIMG = $("#imgid").html();
var imgs = document.getElementsByClassName("image-gallery-item");
var $imgvalues = Array.prototype.map.call(imgs, function (el) {
return el.id;
});
var lastIMG = $('.image-gallery-item').length;
$imgvalues.sort();
index = $imgvalues.indexOf(thisIMG);
if (index >= 1) {
nextItem = $imgvalues[index - 1];
} else {
nextItem = $imgvalues[lastIMG - 1];
}
$('#' + nextItem).trigger('click');
}, 1000);
}
});
I have textarea and storing in array onclick i need to show one by one from the last element and redo onclick one by one from where user clicks. i am doing a custom undo and redo functionality.
var stack =[];
jQuery('#enter-text').keypress(function() {
console.log(jQuery('#enter-text').val());
stack.push(jQuery('#enter-text').val());
})
jQuery('#undo_text').click(function() {
console.log(stack.pop());
})
jQuery('#redo_text').click(function() {
// how to redo onclik where user undo text
})
I have created jsfiddle
https://jsfiddle.net/k0nr53e0/4/
Make an array of old values, so something like this:
var deleted_stack = [];
// your other code
jQuery('#undo_text').click(function() {
deleted_stack.push(stack.pop());
})
jQuery('#redo_text').click(function () {
stack.push(deleted_stack.pop());
}
If you need to be able to do this also in the middle of the text, you should keep track of cursor position. Then your deleted stack should look more like this:
deleted_stack = [
{
char_idx: 2, // index of position in string where it was deleted, but
// then you shouldn't be deleting characters with .pop()
char_val: 'a'
},
... // other objects like first one
]
But then deleting also becomes more complex...
instead if keeping different stacks for the actions you've done, and undone, you can keep them in one Array, and memorize the current position:
var stack = [ jQuery('#enter-text').val() ], index = 0;
updateButtonsDisabled();
jQuery('#enter-text').keypress(function() {
//adding the current action
stack[++index] = jQuery('#enter-text').val();
//removing the entries after the last one you added: they belong to a different redo-stack
stack.length = index+1;
updateButtonsDisabled();
})
jQuery('#undo_text').click(function() {
if(!index) return;
jQuery('#enter-text').val(stack[--index]);
updateButtonsDisabled();
})
jQuery('#redo_text').click(function() {
if(index === stack.length-1) return;
jQuery('#enter-text').val(stack[++index]);
updateButtonsDisabled();
})
//just some sugar
function updateButtonsDisabled(){
jQuery('#undo_text').toggleClass("disabled", index === 0);
jQuery('#redo_text').toggleClass("disabled", index === stack.length-1);
}
index holds the position in stack of the currently shown value. You can undo and redo as much as you want, but as soon as you start typing, the redo-stack will be cleared.
You should consider limiting the items you want to keep in stack, or you'll allocate quite some memory. And you could change the logic for keypress to wait for a pause of like 300ms before you update the stack. That would decrease the items in your stack tremendously.
Edit: made a snippet implementing the possible changes I mentioned, like detached update, and limited stacksize. Take a look at that
//this value is kept small for testing purposes, you'd probably want to use sth. between 50 and 200
const stackSize = 10;
//left and right define the first and last "index" you can actually navigate to, a frame with maximum stackSize-1 items between them.
//These values are continually growing as you push new states to the stack, so that the index has to be clamped to the actual index in stack by %stackSize.
var stack = Array(stackSize),
left = 0,
right = 0,
index = 0,
timeout;
//push the first state to the stack, usually an empty string, but not necessarily
stack[0] = $("#enter-text").val();
updateButtons();
$("#enter-text").on("keydown keyup change", detachedUpdateText);
$("#undo").on("click", undo);
$("#redo").on("click", redo);
//detach update
function detachedUpdateText() {
clearTimeout(timeout);
timeout = setTimeout(updateText, 500);
}
function updateButtons() {
//disable buttons if the index reaches the respective border of the frame
//write the amount of steps availabe in each direction into the data-count attribute, to be processed by css
$("#undo")
.prop("disabled", index === left)
.attr("data-count", index - left);
$("#redo")
.prop("disabled", index === right)
.attr("data-count", right - index);
//show status
$("#stat").text(JSON.stringify({
left,
right,
index,
"index in stack": index % stackSize,
stack
}, null, 2))
}
function updateText() {
var val = $("#enter-text").val().trimRight();
//skip if nothing really changed
if (val === stack[index % stackSize]) return;
//add value
stack[++index % stackSize] = val;
//clean the undo-part of the stack
while (right > index)
stack[right-- % stackSize] = null;
//update boundaries
right = index;
left = Math.max(left, right + 1 - stackSize);
updateButtons();
}
function undo() {
if (index > left) {
$("#enter-text").val(stack[--index % stackSize]);
updateButtons();
}
}
function redo() {
if (index < right) {
$("#enter-text").val(stack[++index % stackSize]);
updateButtons();
}
}
#enter-text {
width: 100%;
height: 100px;
}
#undo,
#redo {
position: relative;
padding-right: 1em;
}
#undo:after,
#redo:after {
content: attr(data-count);
position: absolute;
bottom: 0;
right: 0;
font-size: 0.75em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<textarea id="enter-text"></textarea>
<button id="undo">undo</button>
<button id="redo">redo</button>
<pre id="stat">
</pre>
This is a design decision, and it depends fully on your user needs and how complex you are willing to make it.
The way it is usually done is to keep 2 lists, one for undo, and one for redo.
Every time an action is undone, it is added to the redo list. Every time a new action is taken after undoing, the redo list is destroyed.
var stack = [];
var redo = [];
jQuery('#enter-text').keypress(function() {
console.log(jQuery('#enter-text').val());
stack.push(jQuery('#enter-text').val());
redo = []; // <-----
})
jQuery('#undo_text').click(function() {
redo.push(stack.pop()); // <-----
})
jQuery('#redo_text').click(function() {
stack.push(redo.pop()) // <-----
})
You can use the keyup event and keep always the entered data on the stack array. These way you can update the value of field $('#enter-text') with the appropriate array index that will be updated in the variable index:
var $textEnter = $('#enter-text'),
stack = [],
index = 0;
$textEnter.on('keyup', function () {
stack.push($textEnter.val());
index = stack.length - 1;
});
$('#undo_text').on('click', function () {
if (index >= 0) {
$textEnter.val(stack[--index]);
}
});
$('#redo_text').on('click', function () {
if (index < stack.length - 1) {
$textEnter.val(stack[++index]);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="enter-text"></textarea>
<div id="drag-area-selected">
</div>
<button id="undo_text">
Undo
</button>
<button id="redo_text">
Redo
</button>
I had a question about the way I could execute a callback on multiple elements in my div.
So here it is. I have a div that contains two columns (col1 and col2, both taking 50% of the parent div and on float:left) many projects displayed as images. A big project is twice bigger (height) than a small one.
The configuration created makes it possible to display properly the projects to fill the div and not leave blank parts. So, basically, if we have 3 projects, we get a big project in col1 and 2 small in col2.
With 4 projects, we get a small + big in col1 and a big + small in col2.
And so on.
Problem is, when i'm filtering the different projects (in this example, by, for example, displaying the projects made in JS only, I use show() and hide() which works properly.
If animated, the projects just disappear because the hide() seems to be called while the show() isn't completed yet.
I was then told about the call back function but it seems to work only on one element, not multiples. Which is not optimal since my filtering always displays multiple projects.
I would like to know how to execute the function properly, so it works on all the projects because it seems to apply to only one.
Thanks in advance.
<script>
function update_projects(projects_ids){
console.log("Update projects : " + projects_ids);
var projects_top = $('.projects').offset().top;
if ($(window).scrollTop() > projects_top) {
$(window).scrollTop(projects_top);
}
var projects_config = generate_configuration(projects_ids.length);
var project_index = 0;
//$('.project').show();
$('.project').slideUp(2000, function(){
var odd = false;
for (var i = 0; i < projects_config.col1.length; ++i) {
var block_type = projects_config.col1[i];
var project_id = projects_ids[project_index++];
var odd_selector = '';
if (block_type == 'small') {
if (odd == false) {
odd_selector = '.left';
} else {
odd_selector = '.right';
}
odd = !odd;
}
$('.col1 .project_' + project_id + '.' + block_type + odd_selector).show();
}
odd = false;
for (var i = 0; i < projects_config.col2.length; ++i) {
var block_type = projects_config.col2[i];
var project_id = projects_ids[project_index++];
var odd_selector = '';
if (block_type == 'small') {
if (odd == false) {
odd_selector = '.left';
} else {
odd_selector = '.right';
}
odd = !odd;
}
$('.col2 .project_' + project_id + '.' + block_type + odd_selector).show();
}
});
resize();
}
As you've discovered, the completion callback on an animation is called once per element, not once per set.
To have a callback that is invoked when all of the animations have finished, use the .promise() method on the collection:
$('.myClass').slideUp(2000).promise().then(function() {
// not called until all animations have finished
...
});
I would like to loop through a collection of divs and randomly fade them out when a click event is triggered but at the moment I have to continually click to fade the other divs all out. I would rather click a div and have all its divs randomly fade out. I have added some console.logs into the while loop and everything seems to work fine, problem is when I try to fadeout the actual elements. If anyone could help that would be great?
Fiddle here: http://jsfiddle.net/kyllle/sdpzJ/7/
I'm not sure if I understand your question, but here's a possible solution:
function randomFadeOut(i){
var random;
var e = 0;
while (e < ctnLength) {
random = Math.random() * 1000;
$(ctn[e]).not(i).delay(random).animate({ opacity : 0 });
e++;
}
}
This will fade out all the divs at random times when you click on one.
I updated your fiddle here.
Your random number generator is outside of your loop - so you only get one random number over and over.
Try this:
function randomFadeOut(i){
var random
for (var e=0;e<ctnLength;e++) {
random = Math.floor(Math.random() * ctnLength);
$(ctn[random]).not(i).animate({ opacity : 0 });
}
}
Of course, since this is random, the same cells can be selected more than once, which will leave a number of cells behind.
Decided to throw this out there, too. Simplified.
$(function() {
var $ctn = $('#container .ctn');
function randomFadeOut() {
var $r = $ctn.not($(this));
var e = 0;
while (e < $ctn.length) {
$r.eq(e).delay(Math.random() * 500).animate({ opacity: 0 });
e++;
}
}
$ctn.hide().click(randomFadeOut).each(function(v) {
$(this).delay(50 * v).fadeIn();
});
});
http://jsfiddle.net/sdpzJ/15/
Here is a better and more efficient randomFade function:
function randomFadeOut(i){
var tmp = ctn.toArray();
tmp.sort( function(){ return Math.floor( Math.random()*3 ) -1; } );
for( var i=0; i<tmp.length; ++i ){
$(tmp[i]).delay(100 * i).fadeOut();
}
}
This way, you only go once through the array
I updated your fiddle with it as well to see it in action :)
I have the following function:
function slideDown() {
//get the element to slide
sliding = document.getElementById('slideDiv1');
//add 1px to the height each time
sliding.style.height = parseInt(sliding.style.height)+1+'px';
t = setTimeout(slideDown,30);
if (sliding.style.height == "401px") {
clearTimeout(t);
}
}
which is called within this function:
function addDiv(nextImageSlide) {
//finds the src attribute of the image nested in the Li
elemChild = nextImageSlide.firstChild;
imageSrc = elemChild.getAttribute('src');
//loops and creates six divs which will be the slices. adds background property etc
for (i = 0, j = 0, k = 1; i< = 19; i++, j++, k++) {
var newDiv = document.createElement('div');
newDiv.setAttribute('class', 'new-div');
newDiv.id='slideDiv' + k;
newDiv.style.height = '1px';
newDiv.style.background = 'url(' + imageSrc +') scroll no-repeat - '+39.5 * j + 'px 0';
var a = document.getElementById('content');
a.appendChild(newDiv);
}
slideDown();
}
Which is called within another function that defines nextImageSlide. It later removes all the divs that it just made.
The idea is for an image gallery. When the user hits the next button, I want slices of the next image to slide down to show the next image. Those slices are then taken away and the new image revealed.
I would like something like this: http://workshop.rs/projects/jqfancytransitions/.
It's for an assignment so we have to write all the code ourself and this is the best way I can think to replicate it. The only problem is that I keep getting an error:
'sliding is null. sliding.style.height = parseInt(sliding.style.height)+1+'px';'
No matter what I do I can't get rid of it. The thing is if I define sliding as a totally different id, (for example I made a random little div outside of everything), it working.
This error shows when I try to access the divs, it just made that it throws a hissy fit.
Anyone see any errors in my code?
Hopefully this is just a typo while pasting into the site here, but:
car a = document.getElementById('content');
^---syntax error, which'll kill your entire script - var?