I am building a background img slideshow and running into glitches I can't comprehend.
I have several objects that contains a list of images. I have two functions that will take these images, create one div per each, and add the imgs as background of these divs, all within a container.
Then, as described in this website, I fadeout the first div,and fadeIn the second, then move the first child to last child position, and loop, creating a slideshow effect.
When I want this over i .empty() the container. Then the process can start again with the same or another object.
The first time I do this, it works, but second, third... times, it starts to glitch. Not only two, but all divs start to fade in and out, for I don't know what reason
This happens even if I am using the same object in the first, second, third... attempts.
It would seem as if although the divs are erased from DOM, apparently there is some memory of them? Could it be related to the fact that created divs share the name with previously created divs? maybe fadein out keep some kind of internal queue I am unaware of?
Here is an JsFiddle:
https://jsfiddle.net/93h51k9m/11/
and the code:
$(document).ready(function(){
var imgObject = {
imgs: ['http://lorempixel.com/400/200/sports/1/','http://lorempixel.com/400/200/sports/2/','http://lorempixel.com/400/200/sports/3/']
};
var imgObject2 = {
imgs: ['http://lorempixel.com/400/200/sports/4/','http://lorempixel.com/400/200/sports/5/','http://lorempixel.com/400/200/sports/6/']
};
var noImgObject = {
};
function prepare(index) {
if ($("#cover").css("display") != "none") {
console.log("cover is visible: hide it first");
console.log("fadeOut cover in 3000ms");
$("#cover").fadeOut(3000, function() {
console.log("then empty cover")
$("#cover").empty();
console.log("now for the images")
roll(index);
});
} else {
console.log("cover is already hidden: now for the images");
roll(index);
};
};
function roll(index) {
if (typeof index.imgs != "undefined") {
console.log("called object has images")
console.log("get them and their numbers")
var imgs = index.imgs;
var imgsLength = imgs.length;
console.log("create as many divs as imgs, and place each img as bg in each div")
for (i = 0; i < imgsLength; i++) {
$("#cover").append("<div class='imgdiv" + i + "'></div>");
$(".imgdiv" + i).css("background-image", "url('"+imgs[i]+"')");
};
console.log("now hide all but first div, fadeIn cover and start the carousel");
//as seen at http://snook.ca/archives/javascript/simplest-jquery-slideshow
$('#cover').fadeIn(3000);
$('#cover div:gt(0)').hide();
setInterval(function() {
console.log("fade and swap")
$('#cover :first-child').fadeOut(3000)
.next('div').fadeIn(3000)
.end().appendTo('#cover')
}, 6000);
} else {
console.log("index has no images, nothing to do");
};
};
$("#imgobj").click(function(){
console.log("imgObject called");
prepare(imgObject);
});
$("#imgobj2").click(function(){
console.log("imgObject2 called");
prepare(imgObject2);
});
$("#noimgobj").click(function(){
console.log("noImgObject called");
prepare(noImgObject);
});
});
Thank you
Every time click event is invoked, another interval is being started and that is the reason, actions are appended in the queue
Use global variable which will hold the setInterval instance and clear it every time you start new Interval.
var interval;
$(document).ready(function() {
var imgObject = {
imgs: ['http://lorempixel.com/400/200/sports/1/', 'http://lorempixel.com/400/200/sports/2/', 'http://lorempixel.com/400/200/sports/3/']
};
var imgObject2 = {
imgs: ['http://lorempixel.com/400/200/sports/4/', 'http://lorempixel.com/400/200/sports/5/', 'http://lorempixel.com/400/200/sports/6/']
};
var noImgObject = {};
function prepare(index) {
clearInterval(interval);
if ($("#cover").css("display") != "none") {
console.log("cover is visible: hide it first");
console.log("fadeOut cover in 3000ms");
$("#cover").fadeOut(3000, function() {
console.log("then empty cover")
$("#cover").empty();
console.log("now for the images")
roll(index);
});
} else {
console.log("cover is already hidden: now for the images");
roll(index);
};
};
function roll(index) {
if (typeof index.imgs != "undefined") {
console.log("called object has images")
console.log("get them and their numbers")
var imgs = index.imgs;
var imgsLength = imgs.length;
console.log("create as many divs as imgs, and place each img as bg in each div")
for (var i = 0; i < imgsLength; i++) {
$("#cover").append("<div class='imgdiv" + i + "'></div>");
$(".imgdiv" + i).css("background-image", "url('" + imgs[i] + "')");
};
console.log("now hide all but first div, fadeIn cover and start the carousel");
//as seen at http://snook.ca/archives/javascript/simplest-jquery-slideshow
$('#cover').fadeIn(3000);
$('#cover div:gt(0)').hide();
interval = setInterval(function() {
console.log("fade and swap")
$('#cover :first-child').fadeOut(3000)
.next('div').fadeIn(3000)
.end().appendTo('#cover')
}, 6000);
} else {
console.log("index has no images, nothing to do");
};
};
$("#imgobj").click(function() {
console.log("imgObject called");
prepare(imgObject);
});
$("#imgobj2").click(function() {
console.log("imgObject2 called");
prepare(imgObject2);
});
$("#noimgobj").click(function() {
console.log("noImgObject called");
prepare(noImgObject);
});
});
html {
color: black;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}
body {
height: 100%;
padding: 0;
margin: 0;
background: #f7fafa;
}
* {
box-sizing: border-box;
}
button {
cursor: pointer;
}
#buttons {
z-index: 1000;
}
#cover {
display: none;
position: fixed;
top: 5vh;
left: 0;
width: 100vw;
height: 95vh;
opacity: 0.5;
z-index: 0;
}
#cover div {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-repeat: no-repeat;
background-size: cover;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div id="buttons">
<button id="imgobj">imgObject</button>
<button id="imgobj2">imgObject2</button>
<button id="noimgobj">noImgObject</button>
</div>
<div id="cover"></div>
Related
let i = 0;
function change() {
if (i < res.length) document.getElementById("cont").style.backgroundImage = `url(static/letters/${res[i++]}.jpg)`;
}
window.onload = function() {
setInterval(change, 2000);
};
I want this change only once till all the images get displayed and then want the container empty. What's wrong with my code?
You can check this implementation
let i = 0;
let interval //the variable to keep the current interval
//simulate your `res` data
const res = [
"https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885__480.jpg",
"https://images.unsplash.com/photo-1612151855475-877969f4a6cc?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8MXx8aGQlMjBpbWFnZXxlbnwwfHwwfHw%3D&w=1000&q=80",
"https://www.w3schools.com/w3css/img_lights.jpg"
]
function change() {
if (i < res.length) {
document.getElementById("cont").style.backgroundImage = `url(${res[i++]})`;
} else {
document.getElementById("cont").style.backgroundImage = "none";
clearInterval(interval); //remove the current interval after finish the loop
i = 0; //reset index value
}
}
window.onload = function() {
interval = setInterval(change, 2000); //set the current interval
};
#cont {
height: 300px;
width: 300px;
background-position: center;
background-repeat: no-repeat;
background-size: 300px 300px;
transition: 0.2s;
}
<div id="cont"></div>
This might do the job :
if (i < res.length) document.getElementById("cont").style.backgroundImage = `url('static/letters/${res[i++]}.jpg')`;
Would be really grateful for some advice with this javascript issue I am having with a click event that seems to be doubling every time my slider is closed then reopened.
When you open the slider for the first time and click through the slides you can see in the console the clicks incrementing by 1 every time the 'btn--next' is clicked which is of course correct. When i then close the slider down and re-open it again when the 'btn--next' is clicked the clicks in the console are now incrementing by 2 every click. Close the slider again and re-open and then the 'btn--next' clicks in the console increment by 3 and so on every time the slider is re-loaded.
https://jsfiddle.net/95afhtx8/2/
var loadSlider = document.querySelector('.load__slider');
loadSlider.addEventListener('click', function() {
var slider = document.querySelector('.animal__slider');
var sliderSlide = document.querySelectorAll('.animal__slider__slide');
var nextSlide = document.querySelector('.btn--next');
var previousSlide = document.querySelector('.btn--previous');
var closeSlider = document.querySelector('.animal__slider__close');
var currentSlide = 0;
slider.classList.add('active');
setTimeout(function() {
slider.classList.add('active--show');
startSlide();
}, 100);
//Reset Slider
function resetSlides() {
for (var s = 0; s < sliderSlide.length; s++) {
sliderSlide[s].classList.remove('active--show');
sliderSlide[s].classList.remove('active');
}
}
//Start Slider
function startSlide() {
resetSlides();
sliderSlide[0].classList.add('active');
setTimeout(function() {
sliderSlide[0].classList.add('active--show');
}, 100);
}
//Previous slide
function slidePrevious() {
resetSlides();
sliderSlide[currentSlide - 1].classList.add('active');
setTimeout(function() {
sliderSlide[currentSlide].classList.add('active--show');
}, 100);
currentSlide--;
}
previousSlide.addEventListener('click', function() {
if (currentSlide === 0) {
currentSlide = sliderSlide.length;
}
console.log('click');
slidePrevious();
});
//Next slide
function slideNext() {
resetSlides();
sliderSlide[currentSlide + 1].classList.add('active');
setTimeout(function() {
sliderSlide[currentSlide].classList.add('active--show');
}, 100);
currentSlide++;
}
nextSlide.addEventListener('click', function() {
if (currentSlide === sliderSlide.length - 1) {
currentSlide = -1;
}
console.log('click');
slideNext();
});
closeSlider.addEventListener('click', function() {
slider.classList.remove('active--show');
slider.classList.remove('active');
resetSlides();
});
});
It's because every time you click on your slider toggle:
loadSlider[s].addEventListener('click', function () {
You're re-running code like this, which will add another click handler to the element:
nextSlide.addEventListener('click', function() {
You can add multiple event listeners to any object in the DOM. So you just keep adding more every time the slider opens.
You have three general options here.
Option 1: only set click handlers once
Don't re-add event handlers inside your loadSlider[s].addEventListener('click', function () { function. Do it outside so you aren't re-adding handlers.
Option 2: remove click handlers on close
You can remove the event listeners on close. To do this, you should store a reference to the function you make, so you can explicitly remove it later. You should do this for any handlers you add.
const nextClick = function () {
...
};
nextSlide.addEventListener('click', nextClick);
function resetSlides() {
nextSlide.removeEventListener('click', nextClick);
...
}
This way, when the slider is hidden, the click functionality will be turned off, and re-opening it will add new click handlers and the old ones won't fire because you removed them.
Option 3: Re-create the elements
If you remove an element from the DOM and make a completely new one, the new one won't have stale click handlers on it. This means you'll need to dynamically build your markup with Javascript (using document.createElement), not store it in the HTML page body.
I update your code to work properly (you need to close the anonymous function of the first event listener before you start declaring the others, otherwise you are copying them over and over and therefore the doubling/quadrupling etc...). I would also suggest to move DOM selectors outside of the event listener, they can evaluate only once:
var loadSlider = document.querySelector('.load__slider');
var slider = document.querySelector('.animal__slider');
var sliderSlide = document.querySelectorAll('.animal__slider__slide');
var nextSlide = document.querySelector('.btn--next');
var previousSlide = document.querySelector('.btn--previous');
var closeSlider = document.querySelector('.animal__slider__close');
var currentSlide = 0;
loadSlider.addEventListener('click', function() {
slider.classList.add('active');
setTimeout(function() {
slider.classList.add('active--show');
startSlide();
}, 100);
});
//Reset Slider
function resetSlides() {
for (var s = 0; s < sliderSlide.length; s++) {
sliderSlide[s].classList.remove('active--show');
sliderSlide[s].classList.remove('active');
}
}
//Start Slider
function startSlide() {
resetSlides();
sliderSlide[0].classList.add('active');
setTimeout(function() {
sliderSlide[0].classList.add('active--show');
}, 100);
}
//Previous slide
function slidePrevious() {
resetSlides();
sliderSlide[currentSlide - 1].classList.add('active');
setTimeout(function() {
sliderSlide[currentSlide].classList.add('active--show');
}, 100);
currentSlide--;
}
previousSlide.addEventListener('click', function() {
if (currentSlide === 0) {
currentSlide = sliderSlide.length;
}
console.log('click');
slidePrevious();
});
//Next slide
function slideNext() {
resetSlides();
sliderSlide[currentSlide + 1].classList.add('active');
setTimeout(function() {
sliderSlide[currentSlide].classList.add('active--show');
}, 100);
currentSlide++;
}
nextSlide.addEventListener('click', function() {
if (currentSlide === sliderSlide.length - 1) {
currentSlide = -1;
}
console.log('click');
slideNext();
});
closeSlider.addEventListener('click', function() {
slider.classList.remove('active--show');
slider.classList.remove('active');
resetSlides();
});
.animals {
text-align: center;
position: relative;
width: 80%;
height: 300px;
margin: 0 auto;
background-color: grey;
}
.load__slider {
text-align: center;
}
.animal__slider {
position: absolute;
width: 100%;
height: 100%;
text-align: center;
display: none;
}
.animal__slider.active {
display: block;
}
.animal__slider.active .animal__slider__close {
display: block;
}
.animal__slider.active+.animal__slider__open {
opacity: 0;
}
.animal__slider__slide {
display: none;
position: absolute;
width: 100%;
height: 100%;
}
.animal__slider__slide1 {
background-color: red;
}
.animal__slider__slide2 {
background-color: green;
}
.animal__slider__slide3 {
background-color: yellow;
}
.animal__slider__slide4 {
background-color: blue;
}
.animal__slider__slide.active {
display: block;
}
.btn {
color: black;
position: absolute;
bottom: 5px;
cursor: pointer;
}
.btn--previous {
right: 60px;
}
.btn--next {
right: 30px;
}
.animal__slider__close {
display: none;
position: absolute;
right: 0;
cursor: pointer;
}
.animal__slider__open {
display: block;
cursor: pointer;
}
<section class="animals">
<div class="animal__slider">
Slider
<div class="animal__slider__slide animal__slider__slide1">
slide 1
</div>
<div class="animal__slider__slide animal__slider__slide2">
slide 2
</div>
<div class="animal__slider__slide animal__slider__slide3">
slide 3
</div>
<div class="animal__slider__slide animal__slider__slide4">
slide 4
</div>
<span class="btn btn--previous">previous</span>
<span class="btn btn--next">next</span>
<span class="animal__slider__close">close slider</span>
</div>
<span class="animal__slider__open load__slider">open slider</span>
</section>
In your code, you call nextSlide.addEventListener(...) each time you open the slider, but you never remove that listener. you have to call the function nextSlide.removeEventListener(...) when you close the slider. You also can make sure to call addEventListener only when you open the slider the first time, or even before you open it, as the html element is never destroyed.
To be able to remove the listener, you have to make it accessible in your code when you close the slider. You can't use anonymous functions for this.
EDIT :
An other, simpler solution is to change
nextSlide.addEventListener('click', function(){...});
to:
nextSlide['onclick'] = function() {...};
Can anybody help me to simulate animation of four div tags?
Simply for loop should wait until opacity of div tags change in 1 second.
function animateDiv(ar) { // ar contains div tag indexes. ex:[0,3,2,3,1,0,1,2,3]
for (var i = 0; i < ar.length; i++) {
var ind = "";
if (ar[i] == 0) ind = ".red";
else if (ar[i] == 1) ind = ".blue";
else if (ar[i] == 2) ind = ".yellow";
else if (ar[i] = 3) ind = ".green";
var ok = false;
setTimeout(function () {
$(ind).css('opacity', 1);
console.log("waiting " + " index: " + i);
ok = true;
}, 1000);
if (ok == true) {
$(ind).css('opacity', 0.7);
console.log("Done!");
}
}
}
https://jsfiddle.net/z8y2v5u1/
A for loop can't wait for a timeout to finish, because the function you supply to setTimeout() is run asynchronously after the current function (and whatever called the current function) finishes. So the whole loop will run before the timeout happens.
You need to use a "pseudo-loop" that relies on setTimeout() to trigger the next iteration. Something like the following will work:
function animateDivs(ar, cb) {
var $divs = $(".red,.green,.blue,.yellow"),
i = 0;
(function next() {
if (i === ar.length) {
if (cb) cb();
} else {
var $div = $divs.eq(ar[i]).css('opacity', 1);
setTimeout(function() {
$div.css('opacity', 0.7);
i++;
next();
}, 1000);
}
})();
}
animateDivs([0,3,2,3,1,0,1,2,3], function() { console.log("Finished")});
div { width: 40px; height: 40px; display: inline-block; opacity: 0.7;}
.red { background-color: red;}
.green { background-color: green;}
.blue { background-color: blue;}
.yellow { background-color: yellow;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="red"></div><div class="green"></div>
<br/>
<div class="blue"></div><div class="yellow"></div>
The next() function changes the appropriate div to an opacity of 1, then uses setTimeout() to wait a second before changing the opacity back and then call next() again for the next index in the array.
I figured you might like some way to know when the animation had ended, so I've added a callback argument (cb) to animateDivs() that will be called after the whole array has been processed - in my example all it does is log something to the console, but you could use it to do something more interesting.
Okay I have the following code:-
[SEE JSFIDDLE]
HTML:
<div id="header">
<span class="mobile-menu"></span>
</div>
CSS:
#header {
width: 100%;
background: #000000;
height: 100px;
}
.mobile-menu {
position: absolute;
right: 25px;
top: 20px;
background: url(http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-01.png);
background-repeat: no-repeat;
background-size: 26px !important;
height: 26px;
width: 26px;
display: inline-block;
margin: 7px 0;
-webkit-transition-duration: 0.8s;
-moz-transition-duration: 0.8s;
-o-transition-duration: 0.8s;
transition-duration: 0.8s;
}
.mobile-menu-hover {
background: url(http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/mobile-menu-hover.png);
}
jQuery:
var imagesArray = ["http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-01.png",
"http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-02.png",
"http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-03.png",
"http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-04.png",
"http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-05.png",
"http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-06.png",
"http://planetbounce.m360.co.uk/wp-content/themes/planetbounce/assets/img/buttons/menu-07.png"];
function preloadImg(pictureUrls, callback) {
var i, j, loaded = 0;
var imagesArray = [];
for (i = 0, j = pictureUrls.length; i < j; i++) {
imagesArray.push(new Image());
}
for (i = 0, j = pictureUrls.length; i < j; i++) {
(function (img, src) {
img.onload = function () {
if (++loaded == pictureUrls.length && callback) {
callback(imagesArray);
}
};
img.src = src;
}(imagesArray[i], pictureUrls[i]));
}
};
function changeImage(background, imagesArray, index, reverse) {
background.css("background-image", "url('" + imagesArray[index].src + "')").fadeIn(10, function() {
if (reverse) {
index--;
if (index == -1) {
return; // stop the interval
}
} else {
index++;
if (index == imagesArray.length) {
return; // stop the interval
}
}
//Fade in the top element
background.fadeOut(10, function () {
//Set the background of the top element to the new background
background.css("background-image", "url('" + imagesArray[index] + "')");
changeImage(background, imagesArray, index, reverse);
});
});
}
jQuery(function () {
/* Preload Image */
preloadImg(imagesArray, function (imagesArray) {
jQuery(".mobile-menu").css("background-image", "url('" + imagesArray[0].src + "')")
jQuery('.mobile-menu').on('click', {imgs: imagesArray}, function (event) {
var background = jQuery(".mobile-menu");
var bi = background.css('background-image');
var index = 0;
var reverse = false;
if (imagesArray[0].src != bi.replace('url("', '').replace('")', '')) {
index = imagesArray.length - 1;
reverse = true;
}
changeImage(background, event.data.imgs, index, reverse);
});
});
});
The Issue:
This works fine in Firefox and Chrome, it transitions between the 7 different images on click, then does the reverse on the second click (toggling).
The problem is when I try this in Safari, it basically goes through the image replacement process then reverts back to the first image for some reason and I can't figure out why?
Any ideas?
It seems to be because Safari returns the background-image without double quotes, but your replace function checks for url(", so it doesn't replace and reverse never gets true.
Instead of replacing you can check using either indexOf or matching using a RegExp, it would be safer and more straightforward. Either:
if ( bi.indexOf(imagesArray[0].src) == -1) {
or
if (imagesArray[0].src != bi.match(/http.+png/)[0]) {
With indexOf: http://jsfiddle.net/u9ske14r/
please see this script:
<style type="text/css">
.div1
{
background-color: Aqua;
width: 400px;
height: 30px;
}
.div2
{
background-color: Fuchsia;
width: 400px;
height: 30px;
}
.div3
{
background-color: Green;
width: 400px;
height: 30px;
}
.div4
{
background-color: Orange;
width: 400px;
height: 30px;
}
</style>
<script type="text/javascript">
$(document).ready(function () {
var timer = setInterval(showDiv, 2000);
var counter = 0;
function showDiv() {
if (counter == 0) { counter++; return; }
$('div.My').css('height', '30px');
$('div.My').animate({ height: '30' }, 2000, function () { alert('i'); });
$('div.My')
.stop()
.filter(function () { return this.id.match('div' + counter); })
.animate({ height: '50' }, 500, function () { });
counter == 4 ? counter = 0 : counter++;
}
});
</script>
<body>
<div>
<div class="div1 My" id="div1">
</div>
<div class="div2 My" id="div2">
</div>
<div class="div3 My" id="div3">
</div>
<div class="div4 My" id="div4">
</div>
</div>
</body>
I want every 5 second my div become large and then become normal and next div become large.The problem is first animation does not run and just second animation run.Where is the problem?
JSFiddle Sample
Edit 1)
I want when next div become large previous div become normal concurrently.Not previous become normal and then next become large
Check out my fork of your fiddle and let me know if this is doing what you want. You had a call to .stop() in the middle there, which was blocking the slow shrinking animation from displaying.
Now the full script is:
$(document).ready(function () {
var timer = setInterval(showDiv, 2000);
var counter = 0;
function showDiv() {
if (counter == 0) { counter++; return; }
$('div.My').animate({ height: '30px' }, { duration: 500, queue: false });
$('div.My')
.filter(function () { return this.id.match('div' + counter); })
.animate({ height: '50px' }, { duration: 500, queue: false });
counter == 4 ? counter = 0 : counter++;
}
});
Edit - new Fiddle
I didn't feel right about the above code, and it didn't work as expected in my browser, so I found a different approach that I think works more cleanly. This one uses jQuery's step option. I also use addClass and removeClass as a kind of local storage to remember which div needs to be shrunk on the next animation. You could do some math with counter and get the same result, but this works.
$(document).ready(function () {
var timer = setInterval(showDiv, 2000);
var counter = 0;
function showDiv() {
if (counter == 0) { counter++; return; }
$shrinker = $("div.big").removeClass("big");
$grower = $("#div"+counter);
$grower
.animate({ height:50 },
{duration:500,
step: function(now, fx) {
$shrinker.css("height", 80-now);
}
}
);
$grower.addClass("big");
counter == 4 ? counter = 0 : counter++;
}
});
The step body looks a bit weird, but it guarantees that at each moment of the animation, the total height of the div stack remains constant. Basically, the total height of the shrinking and growing divs (min:30, max:50) has to be 80 at all times, so the height of the shrinking div should be 80 - the height of the growing div.