jQuery hover images carousel timeout overlaps - javascript

I'm building a webpage which shows products. When hovering over the product image, a slideshow-like event should start. You'll see photo's of the product in several states.
I've coded this with jQuery and I'm facing a kinda annoying bug.
When you hover over several products very fast and then leave your mouse on one product, it will slide through the images very fast.
The HTML structure looks like this:
<div class="products">
<div class="productContainer" data-product-id="1">
<img src="http://detestdivisie.nl/upload/images/hover-tests/inside/bmw-inside.jpg" class="mainProductImage" />
<div class="hoverImages">
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/inside/bmw-inside-1.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/inside/bmw-inside-2.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/inside/bmw-inside-3.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/inside/bmw-inside-4.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/inside/bmw-inside-5.jpg" />
</div>
</div>
<div class="productContainer" data-product-id="1">
<img src="http://detestdivisie.nl/upload/images/hover-tests/outside/bmw-outside.jpg" class="mainProductImage" />
<div class="hoverImages">
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/outside/bmw-outside-1.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/outside/bmw-outside-2.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/outside/bmw-outside-3.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/outside/bmw-outside-4.jpg" />
<img src="" data-lazy-src="http://detestdivisie.nl/upload/images/hover-tests/outside/bmw-outside-5.jpg" />
</div>
</div>
</div>
The Javscript class that is responsible for handling the events looks like this:
CategoryProductImages = function () {
this.className = 'CategoryProductImages';
this.version = '0.1.0';
this.mouseOver = false;
this.currentElement = null;
this.currentCategoryId = 0;
this.currentImageList = [];
this.currentImageKey = 0;
/**
* CategoryProductImages.init()
*
* Initializes the class that is reponsible for handling the
* mouse over and outs for the products.
*
* This method will call the createObserver method.
*/
this.init = function () {
console.log('Called ' + this.className + '.init()');
this.createObservers();
};
/**
* CategoryProductImages.createObservers()
*
* Will handle the mouse over and out events.
*/
this.createObservers = function () {
console.log('Called ' + this.className + '.createObservers()');
var thisObj = this;
jQuery('.productContainer').hover(
/** Called when mouse of the user moves over the element. **/
function () {
console.log('Mouse over .productContainer');
thisObj.mouseOver = true;
thisObj.currentElement = jQuery(this);
thisObj.currentCategoryId = thisObj.currentElement.data('category-id');
console.log('Lets\'s work for category id ' + thisObj.currentCategoryId);
thisObj.currentElement.find('.hoverImages img').each(function() {
thisObj.currentImageList.push(jQuery(this).data('lazy-src'));
});
thisObj.iterate();
},
/** Called immediatly after the mouse of the user leaves the element **/
function () {
console.log('Mouse out .productContainer');
thisObj.currentElement = null;
thisObj.mouseOver = false;
thisObj.currentImageList = new Array();
thisObj.currentImageKey = 0
}
);
};
this.iterate = function () {
console.log('Called ' + this.className + '.iterate()');
if (this.mouseOver && this.currentImageList.length > 0) {
console.log('Will now start the iteration process');
this.currentElement.find('img.mainProductImage').prop('src', this.currentImageList[0]);
thisObj = this;
setTimeout(function () {
console.log('First image shown, will now show next image.');
thisObj.nextImage(thisObj.currentCategoryId);
}, 3000)
} else {
console.log('Won\'t iterate, because the mouse of the user has left the element, of there are no images to show.');
}
};
this.nextImage = function (currentCategoryId) {
console.log('Called ' + this.className + '.nextImage()');
if (this.mouseOver && this.currentImageList.length > 0 && currentCategoryId == this.currentCategoryId) {
console.log('MouseOver still active, and images are found, show next image.');
this.currentImageKey += 1;
if (typeof this.currentImageList[this.currentImageKey] == 'undefined') {
console.log('OH NO! We\'ve reached the end of the list. Letst start all over again.');
this.currentImageKey = 0;
}
this.currentElement.find('img.mainProductImage').prop('src', this.currentImageList[this.currentImageKey]);
thisObj = this;
setTimeout(function () {
console.log('Okay, we\'ve waited for three seconds, NEXT! :)');
thisObj.nextImage(currentCategoryId)
}, 3000);
} else {
console.log('Iteration for ' + currentCategoryId + ' stopped');
console.log('Mouse over: ');
console.log(this.mouseOver);
console.log('Image list length: ');
console.log(this.currentImageList.length);
console.log('nextImage category id: ');
console.log(currentCategoryId);
console.log('Current category id:');
console.log(this.currentCategoryId);
console.log('#########################');
}
}
};
var categoryProductImagesObject = new CategoryProductImages();
categoryProductImagesObject.init();
The make it all more clear I've created a CodePen example. Please mouse over product 1 and product 2 very fast and then leave your mouse on product 1. You'll see it will loop through the product images way to fast.
http://codepen.io/wdivo/pen/lamsq
The time it should take before and image is replaced by another one is 3 seconds.
I am overlooking something, because obviously several setTimeouts are working near eachother, while there should only be 1 active setTimeout.
What should I do to make sure only 1 "active" setTimeout is running?
Simply said, I'd want to stop all the previous setTimeouts if a new one gets activated.
I'm now looking into clearInterval, but can't think of a way to implement it...

Related

why the control button do not work in my slideshow?

I make slideshow width 4 photos that appear with opacity: 1 and z-index: 2, and I could make it run automatically, but to control it, not yet and this is my js code with some of jquery:
$(document).ready(function() {
var i = 0
function next() {
move(i++);
if (i === 4) {
i = 0
}
console.log("first i = " + i)
};
setInterval(next, 2000);
function move(n) {
var images = document.querySelectorAll('img')
var img = images[n]
$(img).addClass('showSlide')
$(img).removeClass('hideSlide')
$(img).siblings(".img").addClass('hideSlide')
}
$('.next').click(
() => {
if (i === 3) {
i = 0
};
move(i++);
console.log("next i = " + i)
}
)
$('.previous').click(
() => {
if (i === 0) {
i = 3
};
move(i--);
console.log("previous i = " + i)
}
)})
my automatic slide work but when I click the next or the previous button the slide do not continue from the last position ,and my HTML code is :
<div class="container">
<button class="next">next</button>
<button class="previous">previous</button>
<img class="img" src="gallery-img7.jpg" alt="">
<img class="img" src="gallery-img2.jpg" alt="">
<img class="img" src="gallery-img8.jpg" alt="">
<img class="img" src="gallery-img3.jpg" alt="">
</div>
I think the way you are handling increment and decriment might be the issue? This is a good use case for modulo %. I also cleared and reset the interval after button click to get the same interval on the newly shown image. Here's an example that seems to work as your intending:
$(document).ready(function() {
var i = 0
function next() {
move(i++);
};
let nextInterval = setInterval(next, 2000);
function move(n) {
clearInterval(nextInterval)
nextInterval = setInterval(next, 2000);
n = i%4;
var images = document.querySelectorAll('img')
var img = images[n]
$(img).addClass('showSlide')
$(img).removeClass('hideSlide')
$(img).siblings(".img").addClass('hideSlide')
}
$('.next').click(
() => {
move(i++);
}
)
$('.previous').click(
() => {
move(i--);
}
)})
.img{
display:block;
width:100px;
height:100px;
}
.showSlide{
display:block;
}
.hideSlide{
display:none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<button class="next">next</button>
<button class="previous">previous</button>
<img class="img" src="https://via.placeholder.com/100x100.png?text=1" alt="">
<img class="img hideSlide" src="https://via.placeholder.com/100x100.png?text=2" alt="">
<img class="img hideSlide" src="https://via.placeholder.com/100x100.png?text=3" alt="">
<img class="img hideSlide" src="https://via.placeholder.com/100x100.png?text=4" alt="">
</div>
I think your code to trigger the next and previous clicks is basically working, the Interval function you are running is never interrupted by your button clicks, so the slideshow continues to cycle around.
Your classes to show and hide may not be attaching to the DOM properly either. I find it's a good practice to attach less specific classes before I attach a specific one, i.e. blanket hide all slides and then show the selected slide.
Another technique that I think is helpful is to try and figure out the manual user interaction first and then base my automation on it. I've worked up an alteration of the code you posted, where the slides 'slide' themselves by triggering the next action, similarly to how a user would.
So the slideshow should start itself on page load by use of a setInterval being declared. That setInterval is interrupted when the user moves the mouse into the slideshow area - this way the button will control the active/shown slide. If you move the mouse off or away from the slideshow container the setInterval is allowed to kick in again, and the slides should cycle around automatically.
$(document).ready(function() {
var i = 0;
function move(n) {
var images = document.querySelectorAll('img');
var img = images[n];
$(img).siblings(".img").addClass('hideSlide');
$(img).siblings(".img").removeClass('showSlide');
$(img).addClass('showSlide');
$(img).removeClass('hideSlide');
}
var next = setInterval(autoRotate, 2000);
function autoRotate() {
$('.next').trigger('click');
}
$('.container').on('mouseenter', function() {
clearInterval(next);
});
$('.container').on('mouseleave', function() {
next = setInterval(autoRotate, 2000);
});
$('.next').click(() => {
if (i === 3) {
i = 0;
} else {
i++;
}
move(i);
});
$('.previous').click(() => {
if (i === 0) {
i = 3;
} else {
i--;
}
move(i);
});
})

Display 6 (distinct) images from an array, don't show duplicates and reload every x seconds

I have 12 images in total. I want to display a maximum of 6 distinctive images at any one time - without showing duplicates. To do this I am using jQuery. I want the images to change/rotate every 5 seconds.
I can't seem to get this code to work. Can someone please show me where I am going wrong?
var imagesArray = [
imgs / Scan1.jpeg ',
'../imgs/Scan2.jpeg',
'../imgs/Scan3.jpeg',
'../imgs/Scan4.jpeg',
'../imgs/Scan5.jpeg',
'../imgs/Scan6.jpeg',
'../imgs/Scan7.jpeg',
'../imgs/Scan8.jpeg',
'../imgs/Scan9.jpeg',
'../imgs/Scan10.jpeg',
'../imgs/Scan11.jpeg',
'../imgs/Scan12.jepg',
];
var usedImages = {};
var usedImagesCount = 0;
function displayImage() {
var num = Math.floor(Math.random() * (imagesArray.length));
if (!usedImages[num]) {
document.canvas.src = imagesArray[num];
usedImages[num] = true;
usedImagesCount++;
if (usedImagesCount === imagesArray.length) {
usedImagesCount = 0;
usedImages = {};
}
} else {
setInterval(function() {
$("#deluxe_img").attr("src", displayImage(););
}, 5000);
}
}
<div class="premium_listing_container">
<img src="function(displayImage())" id="deluxe_img" />
<img src="function(displayImage())" id="deluxe_img" />
<img src="function(displayImage())" id="deluxe_img" />
<img src="function(displayImage())" id="deluxe_img" />
<img src="function(displayImage())" id="deluxe_img" />
<img src="function(displayImage())" id="deluxe_img" />
</div>
As Christian Hain pointed out in the comment, id are supposed to be unique while you have 6 img with the same id. You must use classes for these situations.
If you use the string function(displayImage()) as attribute, your browser will look for a image called exactly function(displayImage()) and will not evaluate it as Javascript, because it doesn't understand it is javascript code. You need to use the javascript line for changing them also on the first page load to set the initial values:
$(".deluxe_img").attr("src", displayImage());
Why do you set the interval everytime that !usedImages[num] evaluates to false? This will keep generating new interval, so your code will constantly be running and not only once every 5 seconds. You need to handle the case when !usedImages[num] evaluates to false by looking for a different num
var imageIndexes = [0,1,2,3,4,5,6,7,8,9,10,11]
function displayImage() {
var index = Math.floor(Math.random() * (imageIndexes.length));
var num = imageIndexes[index]
var result = imagesArray[num];
imageIndexes.splice(index, 1)
if (imageIndexes.length === 0) {
imageIndexes = [0,1,2,3,4,5,6,7,8,9,10,11]
}
return result
}
function changeImagesSrc() {
$(".deluxe_img").each(function () {
$(this).attr('src',displayImage())
})
}
$(document).ready(function () {
changeImagesSrc()
setInterval(function() {
changeImagesSrc()
}, 5000);
})

The slideshow works well if I stay in the same browser tab, and becomes not well If I moved to another tab

My slideshow works well if I stay in the same browser tab which slideshow exists in, but If I change the Browser tab to another and then back again to the slideshow tab I find that the slideshow moves randomly And in a wrong way. I don't know what happened to the slideshow.
HTML:
<div id="slideshow">
<img src="images/img.jpg" alt="" title="" />
<img src="images/img2.jpg" alt="" title="" />
</div>
Javascript:
var time = 0;
var slideshow = function() {
var slideshowElem = document.getElementById('slideshow');
var slideshowLen = slideshowElem.children.length;
var slideshowScrollheight = slideshowElem.scrollHeight - 300;
if (slideshowElem.scrollTop < slideshowScrollheight) {
time = setInterval(function() {
slideshowElem.scrollTop += 5;
if (slideshowElem.scrollTop == slideshowScrollheight)
clearInterval(time);
}, 10);
} else {
time = setInterval(function() {
slideshowElem.scrollTop -= 5;
if (slideshowElem.scrollTop == 0)
clearInterval(time);
}, 10);
}
setTimeout(slideshow, 3000);
}
// implementation
slideshow();
Can anybody know why that happened, although I think my code is correct?

jQuery image Grid System

I have one question about image grid system.
I created this DEMO from codepen.io
In this demo you can see :
<div class="photo-row">
<div class="photo-item">
<!--Posted image here <img src="image/abc.jpg"/>-->
</div>
</div>
This DEMO is working fine but. My question is how can I use my grid system like in this css:
<div class="photo">
<div class="photo-row">
<img src="abc.jpg"/>
</div>
<div class="photo-row">
<img src="abc.jpg"/>
</div>
</div>
I created second demo for this: second DEMO. In the second demo you can see the grid system not working like first DEMO.
Also my jQuery code:
(function($,sr){
var debounce = function (func, threshold, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
};
if (timeout)
clearTimeout(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = setTimeout(delayed, threshold || 100);
};
}
// smartresize
jQuery.fn[sr] = function(fn){ return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };
})(jQuery,'smartresize');
/* Wait for DOM to be ready */
$(function() {
// Detect resize event
$(window).smartresize(function () {
// Set photo image size
$('.photo-row').each(function () {
var $pi = $(this).find('.photo-item'),
cWidth = $(this).parent('.photo').width();
// Generate array containing all image aspect ratios
var ratios = $pi.map(function () {
return $(this).find('img').data('org-width') / $(this).find('img').data('org-height');
}).get();
// Get sum of widths
var sumRatios = 0, sumMargins = 0,
minRatio = Math.min.apply(Math, ratios);
for (var i = 0; i < $pi.length; i++) {
sumRatios += ratios[i]/minRatio;
};
$pi.each(function (){
sumMargins += parseInt($(this).css('margin-left')) + parseInt($(this).css('margin-right'));
});
// Calculate dimensions
$pi.each(function (i) {
var minWidth = (cWidth - sumMargins)/sumRatios;
$(this).find('img')
.height(Math.floor(minWidth/minRatio))
.width(Math.floor(minWidth/minRatio) * ratios[i]);
});
});
});
});
/* Wait for images to be loaded */
$(window).load(function () {
// Store original image dimensions
$('.photo-item img').each(function () {
$(this)
.data('org-width', $(this)[0].naturalWidth)
.data('org-height', $(this)[0].naturalHeight);
});
$(window).resize();
});
Anyone can help me in this regard ? Thank you in advance for your answer.
Since you'll be creating the HTML dynamically you should remove the .photo-row container but keep .photo-item like so:
<div class="photo-item">
<img src="..." />
</div>
<div class="photo-item">
<img src="..." />
</div>
<div class="photo-item">
<img src="..." />
</div>
...
Then what you can do is wrap your elements with .photo-row on page load. First starting with various sets of two:
var imgGrab = $('.photo-item'); //photos
var imgLength = imgGrab.length; //number of photos
for ( i=0; i<imgLength; i=i+3 ) {
imgGrab.eq(i+1).add( imgGrab.eq(i+1) ).add( imgGrab.eq(i+2) ).wrapAll('<div class="photo-row"></div>'); //wrap photos
}
Then find the remanding ones and wrap those with .photo-row as well:
$(".photo-item").each(function(){
if($(this).parent().is(":not(.photo-row)")){
$(this).wrap('<div class="photo-row"></div>');
}
});
This will wrap your images dynamically and let the CSS do its job regardless of the number of them:
CODEPEN

Switching image with Next/Previous buttons

Is there any way to switch images using next/prev buttons with jQuery? Here's the code:
<div class="prevDiv">
<img src="images/prev.png" alt="previous" />
</div>
<div class="pic" id="picwebd">
<img src="images/portfolio/webdesign/webd1.jpg" class="portPic" />
</div>
<div class="nextDiv">
<img src="images/next.png" alt="previous" />
</div>
I tried modifying this code to my needs: http://jsfiddle.net/x5mCR/16/ but I haven't succeed. I think that incrementing and decrementing number in the image src would be enough, but I can't come up with decent code do this. Google doesn't help neither.
In case anyone reading this post want a different approach still using JQuery fadeIn, Im posting below the code for that.
Here you can find the fiddle for it.
Here's the Javascript Part
//At the start will show the first image
$('#fullimage img.fullimage:first-child').fadeIn();
//Keep track of the image currently being visualized
var curr = $('#fullimage img.fullimage:first-child');
$('#next').on('click', function() {
//Hide Current Image
curr.hide();
//Find Next Image
var next = curr.next();
//If theres no next image (is the last img), go back to first
if (next.length == 0) next = $('#fullimage img:first');
//Fade In
next.fadeIn();
//Save in curr the current Image
curr = next;
return false;
});
$('#prev').on('click', function() {
curr.hide();
var prev = curr.prev();
if (prev.length == 0) prev = $('#fullimage img:last');
prev.fadeIn();
curr = prev;
return false;
});
Here's the HTML part
<div id="fullimage">
<img class="fullimage" src="http://i.imgur.com/RHhXG.jpg" />
<img class="fullimage" src="http://i.imgur.com/p1L2e.jpg" />
<img class="fullimage" src="http://i.imgur.com/NsrI0.jpg" />
<img class="fullimage" src="http://i.imgur.com/Ww6EU.jpg" />
</div>
<label id="prev">previous</label>
<label id="next">next</label>
Here is dynamic and simple script reducing your html code
http://jsfiddle.net/x5mCR/32/
$("#thumbnail a").on('click', function (eve) {
eve.preventDefault();
var link = ($(this).attr("href"));
var content = '<img src="' + link + '"/>';
$("#fullimage").hide().html(content).fadeIn('slow');
});
Remove the anchors with class thumbnail and give the corresponding <img> tags the thumbnail class, then use jQuery click methods for the thumbnail class:
$(".thumbnail").click(function() {
$(".fullimage").src = $(this).attr("src");
});
Make sure you have a single .fullimage in the #fullimage div.
This isn't the same as a next / previous button - but it would fix the JSFiddle that you made.
http://jsfiddle.net/x5mCR/34/
var picNumber = 1;
$(".nextDiv").click(function() {
picNumber++;
if (picNumber > 3) picNumber = 1; // Change 3 to how many pictures there are.
$(".pic img").attr("src", "images/portfolio/webdesign/webd" + picNumber + ".jpg");
});
$(".prevDiv").click(function() {
picNumber--;
if (picNumber < 1) picNumber = 3; // Change 3 to how many pictures there are.
$(".pic img").attr("src", "images/portfolio/webdesign/webd" + picNumber + ".jpg");
});
If I understand correctly, you're trying to use the arrow keys to move back and forth between pictures...so if this is the case, I would recommend taking a look at this post: Binding arrow keys in JS/jQuery
Also, for your convenience, I just took the code from that post and combined it with the function in your fiddle to get this:
$(document).keydown(function (e) {
if (e.keyCode == 39) {
$(".fullimage").hide();
var next = $(this).next();
if (next.length > 0) {
next.fadeIn();
} else {
$('#fullimage img:first').fadeIn();
}
return false;
}
});
In testing it looks like it might need some modification, and also, you would obviously need to create a similar function for when the back button is pressed, but I think if I'm understanding your issue correctly this is a good starting place.

Categories