Juozas Rastenis
Juozas Rastenis

Reputation: 265

JS DOM manipulation 'mouseleave' triggers unexpectedly in Safari browser

EDIT: 'mouseleave' event is constantly being triggered, although the mouse does not leave the element.

Code works as intended in: chrome, mozilla, edge, opera. But not safari!

I have a vanilla JavaScript solution that changes images every 1000ms when mouse hovered on parent element. There can be any amount of images inside wrapper and this should still work. To be more clear, javascript adds "hidden" class for every image and removes it from the one who's turn is to be displayed. (Code is in fiddle).

In safari it seems to be stuck swapping 2-3rd image. Am I using wrong dom-manipulation approach? How can I find the error?

Problem presentation: https://jsfiddle.net/pcwudrmc/65236/

let imageInt = 0;
let timeOut;
let imagesWrapper = document.querySelectorAll('.items-box__item');

// Events for when mouse enters/leaves
imagesWrapper.forEach(el => {
    el.addEventListener('mouseenter', () => startAnim(el));
  
    el.addEventListener('mouseleave', () => stopanim(el));
});

// DOM Manipulation functions
function changeImages(el) {
    imageInt += 1;
    if (imageInt === el.children[0].children.length) {
      // reset to 0 after going through all images
      imageInt = 0;
    }
    for (let i = 0; i < el.children[0].children.length; i++) {
      // Adds "hidden" class to ALL of the images for a product
      el.children[0].children[i].classList.add('hidden');
    }
    // Removes "hidden" class for one
    el.children[0].children[imageInt].classList.remove('hidden');
    // changeImage calls itself again after 1 second, if hovered
    timeOut = setTimeout(changeImages.bind(null, el), 1000);
}

function changeBack(el) {
  for (let i = 0; i < el.children[0].children.length; i++) {
    // Adds "hidden" class to ALL of the images for a product
    el.children[0].children[i].classList.add('hidden');
  }
  // Removes "hidden" class for the first image of the item
  el.children[0].children[0].classList.remove('hidden');
}

startAnim = element => { changeImages(element) }

stopanim = element => {
  changeBack(element);
  clearTimeout(timeOut);
  imageInt = 0;
}
.items-box__item {
		width: 300px;
    height: 300px;
}
.items-box__item--main-image {
    object-fit: contain;
	width: 90%;
	height: 265px;
}
.hidden {
	display: none;
}
<h3>Hover on pic and hold mouse</h3>
<div class="items-box__item">
  <a href="/">
      <img class="items-box__item--main-image" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/yrllszgndxzlydbycewc.jpg"/>
      <img class="items-box__item--main-image hidden" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/e96i5zbvxxuxsdczbh9d.jpg"/>
      <img class="items-box__item--main-image hidden" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948252/boaqfs3yuc4r7mvhsqqu.jpg"/>
  </a>
</div>

Upvotes: 0

Views: 446

Answers (1)

Kosh
Kosh

Reputation: 18378

You need to look at relatedTarget of mouseleave event, as both mouseenter and mouseleave happen every time the displayed image changes.

Also your code might be simplified. See the snippet below. Hope it helps.

const play = (box) => {
  while (!box.classList.contains('items-box__item')) box = box.parentElement;
  var img = box.querySelector('.show');
  img.classList.remove('show');
  (img.nextElementSibling || box.firstElementChild).classList.add('show');
}

const stop = ({target: box, relatedTarget: rt}) => {
  while (!box.classList.contains('items-box__item')) box = box.parentElement;
  while (rt != box && rt) rt = rt.parentElement;
  if (rt === box) return;
  box.querySelector('.show').classList.remove('show');
  box.firstElementChild.classList.add('show');
  box.play = clearInterval(box.play);
}

[...document.querySelectorAll('.items-box__item')]
.forEach((box) => {
  box.addEventListener(
    'mouseenter', 
    function() {
      if (box.play) return;
      play(box);
      box.play = setInterval(() => play(box), 1000);
    }
  );
  
  box.addEventListener('mouseleave', stop);
});
.items-box__item {
  display: block;
  width: 300px;
  height: 300px;
}

.items-box__item img {
  object-fit: contain;
  width: 90%;
  height: 265px;
  display: none;
}

img.show {
  display: initial
}
<h3>Hover on pic and hold mouse</h3>
<a class="items-box__item" href="/">
  <img class="show" src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/yrllszgndxzlydbycewc.jpg">
  <img src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948251/e96i5zbvxxuxsdczbh9d.jpg">
  <img src="https://res.cloudinary.com/keystone-demo/image/upload/c_limit,h_300,w_300/v1525948252/boaqfs3yuc4r7mvhsqqu.jpg">
</a>

Upvotes: 1

Related Questions