animationEnd Eventlistener fires multiple times - javascript

Html:
<div id="click">Click</div>
<div id="animateElement">I get animated</div>
CSS:
#animateElement {
height: 100px;
height: 100px;
background: green;
}
.animate {
animation: blue 2s linear forwards;
}
#keyframes blue {
from {
background: red;
}
to {
background: blue;
}
}
Javascript:
document.getElementById('click').addEventListener('click', function (e) {
document.getElementById('animateElement').classList.toggle('animate');
document.getElementById('animateElement').addEventListener('animationend',function(e){
alert('animated');
});
}, false);
First Click is ok, next Clicks fires twice, third click 4 times etc.... Is there a way to prevent this?
I tried to remove the eventlistener but i had no success...

Each time the click event listener is called, an additional animationend event listener is added to #animateElement. You can resolve this by de-nesting the registering of event listeners:
document.getElementById('click').addEventListener('click', function (e) {
document.getElementById('animateElement').classList.toggle('animate');
}, false);
document.getElementById('animateElement').addEventListener('animationend',function(e) {
alert('animated');
});
You might be interested in reading up on how event listeners work on the MDN Web Docs.

Related

How to make click event trigger from a mousedown that was triggered manually?

I currently have a popped-up context menu. This popup gets closed when you click outside of it.
This is done with a div behind the popup that takes up the whole window, and listens for the mousedown event.
I need this mouse event to be sent to elements on the page behind the layer, so I call trigger() on those.
function onLayerMouseDown(e) {
layer.hide();
$("#menu").hide();
target = document.elementFromPoint(e.pageX - $(window).scrollLeft(), e.pageY - $(window).scrollTop());
if (target != null)
$(target).trigger(e);
This works well for the mousedown and mouseup events, but the click event which would come when the mouse presses and releases without leaving the element is never sent.
How can I make it so the click event gets sent too?
See this fiddle for an example with the layer. Click the text behind while the menu is open and see the console.
The problem is easy to understand, but the solution will depend on your requirements. Once the mousedown event is sent, the first thing that the script does is to hide the layer. This means that it will no longer detect mouse events. This means that no click event is ever emitted, neither for the layer (the click event would need a mouseup inside the same element) nor for the inside (the mousedown event received is a copy and comes from layer). It is important to notice that the click event is managed by the browser. You cannot trigger a click by just sending a mousedown and then a mouseup. That also explains why inside receives a mouseup event, as this event only happens after layer has disappeared.
Regarding how to solve it, you might:
Set the layermousedown function to handle the click event. With this design, it would mean that mousedown and mouseup are not captured.
Change the design. The idea would be to set the pointer-events of layer to none:
$("#inside").on("mousedown", (e) => {
console.log("inside mousedown (triggered manually from the layer)");
});
$("#inside").on("click", (e) => {
console.log("inside click (should happen right before mousup, if the mouse stayed inside the element");
});
$("#inside").on("mouseup", (e) => {
console.log("inside mouseup (triggered automatically when releasing mouse button)");
});
$("#button").click(() => {
createlayer();
return false;
});
$(document).on('click', layermousedown);
$("#menu").click(() =>{return false});
function layermousedown(e) {
$("#layer").hide();
$("#menu").hide();
$("#button").prop('disabled', false);
}
function createlayer() {
$("#button").prop('disabled', true);
$("#layer").show();
$("#menu").show();
console.log("added the invisible layer");
console.log("_______________________");
}
createlayer();
#layer {
height: 100vh;
width: 100vw;
display: block;
position: fixed;
z-index: 1;
top: 0;
left: 0;
opacity: 0;
filter: alpha(opacity=0);
background-color: #000;
pointer-events: none;
}
#menu {
display: block;
position: fixed;
z-index: 2;
top: 20px;
left: 50px;
background-color: #ff5;
width: 50px;
height: 100px;
border: 1px solid black;
pointer-events: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
<span id="inside">click me please</span>
</div>
<div id="layer"></div>
<button id="button">
Add the layer again
</button>
<div id="menu">
<span>this is part of the menu</span>
</div>
The idea is to react to document events and intercept the click events in the elements that should not be responsive. That is why some callbacks return false.

Why the event trigger immediately when I just dispatch it?

I would like do something when i click element #b then dispatch an other event listener before handleB() finished
When I was clicked #b the event click on #a has dispatch successful and it has also triggered but I don't wanna trigger it
What's going on here??
document.getElementById('b').addEventListener('click', handleB)
function handleB() {
alert('Handle B!');
document.getElementById('a').addEventListener('click', handleA)
}
<div id="a">
AAA
<div id="b">BBB</div>
</div>
This is happening because of the event of #b bubbles up to #a and since you have added the click event listener inside handleB. It triggers immediately with the event of #b. You have to stop the event to bubble up. Below is the working example:
document.getElementById('b').addEventListener('click', handleB);
function handleB(event) {
event.stopPropagation();
console.log('b clicked');
document.getElementById('a').addEventListener('click', handleA)
}
function handleA() {
console.log('a clicked');
}
#a {
width: 200px;
height: 200px;
background-color: red;
}
#b {
width: 100px;
height: 100px;
background-color: green;
}
<div id="a">
<div id="b"></div>
</div>

Prevent particular child element from firing parent's mouseover event in jQuery

I have parent element which has mouseover event handler implemented using .mouseover() in Jquery.
It has 2 child elements, one contains image element, and second contains description element which has absolute position. On parent mouseover description slides on the image element.
Simplified version of code for the project would look like this:
$('.main-parent').mouseenter(function(){
$(this).addClass('item-hovered');
}).mouseleave(function(){
$(this).removeClass('item-hovered');
});
.main-parent {
position: relative;
}
.child-description {
color: #fff;
font-size: 2em;
position: absolute;
bottom: -45%;
opacity: 0;
transition: all 350ms;
}
.item-hovered .child-description {
bottom: 10%;
opacity: 1;
transition: all 350ms;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="main-parent">
<div class="child-image">
<img src="https://www.w3schools.com/w3css/img_lights.jpg">
</div>
<div class="child-description">
<h4 class="title">Title</h4>
<p class="subtitle">Subtitle</p>
<p>Lorem ipsum paragraph...</p>
</div>
</div>
As expected, .child-description is firing mouseover bind to .main-parent element, as it is its child and a part of it.
Is there a way to ignore .child-description element so that it doesn't fire function on mouseover event. The thing is before you hover the element, it is bellow the image made "invisible" to user using opacity: 0;, but it still can be hovered and used to fire mouseover of parent element.
I haven't find answer for this particular solution on stackoverflow, and if there is let me know. I appreciate your help :)
Yes, you would intercept the event for that child and then call event.preventDefault() and event.stopPropagation() as shown below.
Also, JQuery no longer recommends event shortcut methods, like mouseenter. Instead, they suggest using on().
$(".child-description").on("mouseover", function(event){
event.preventDefault(); // Cancel the event for this element
event.stopPropagation(); // Prevent the event from propagating to other elements
});
$('.main-parent').on("mouseenter", function(){
$(this).addClass('item-hovered');
}).on("mouseleave", function(){
$(this).removeClass('item-hovered');
});
.main-parent {
position: relative;
}
.child-description {
color: #fff;
font-size: 2em;
position: absolute;
bottom: -45%;
opacity: 0;
transition: all 350ms;
}
.item-hovered .child-description {
bottom: 10%;
opacity: 1;
transition: all 350ms;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="main-parent">
<div class="child-image">
<img src="https://www.w3schools.com/w3css/img_lights.jpg">
</div>
<div class="child-description">
<h4 class="title">Title</h4>
<p class="subtitle">Subtitle</p>
<p>Lorem ipsum paragraph...</p>
</div>
</div>
check whether child-description is the target of the event
$('.main-parent').mouseenter(function (e) {
if (!$(e.target).hasClass('child-description')) {
$(this).addClass('item-hovered');
}
}).mouseleave(function () {
$(this).removeClass('item-hovered');
});
Trying adding the following css rule to .child-description.
pointer-events: none;
.child-description {
color: #fff;
font-size: 2em;
position: absolute;
bottom: -45%;
opacity: 0;
transition: all 350ms;
pointer-events: none;
}
.item-hovered .child-description {
bottom: 10%;
opacity: 1;
transition: all 350ms;
pointer-events: auto;
}
This should prevent the element from responding to any mouse events. You will have to swap pointer-events: none; for pointer-events: auto; once you want the element to register interactions.
https://caniuse.com/#feat=pointer-events
I see some interesting answers here, so, I thought I would offer my own.
Why not use event.target and (in this particular case) event.target.className? Sample JSBIN Demo Online
For instance...
$('.main-parent').mouseover(function(e) {
if(e.target.className == 'subtitle') {
console.log("Child mouseover!");
return; // child mouseover, ignore
}
console.log("Parent mouseover!");
return true; // parent mouseover, activate some behavior
});
The advantage here should be observable -- you have quick, easy control of the paths of logic in relatively little code.
You should be able to prevent this using the stopPropagation method:
$('.child-description').on("mouseenter mouseleave", function(event) {
event.stopPropagation();
});

JS EventListener animationend firing too early

I need to change the width and height of an element using js with a smooth transition. My idea was to add a class to the element which makes the transition smooth, change the width and height, and once the transition is done, removing the class again. I use the following code:
element.classList.add("smoothTransition")
element.classList.toggle("fullscreen")
element.addEventListener("webkitAnimationEnd", element.classList.remove("smoothTransition"));
element.addEventListener("animationend", element.classList.remove("smoothTransition"));
Sadly no transition is happening. Without the eventListener the transition is happening. Also the eventListener does trigger, right after the transition starts.
Your issue is in your addEventListener:
element.addEventListener("webkitAnimationEnd", element.classList.remove("smoothTransition"));
element.addEventListener("animationend", element.classList.remove("smoothTransition"));
The second argument of addEventListener must be a a function and not the result of a function call (in your case undefined). Hence, change the previous lines to:
element.addEventListener("webkitAnimationEnd", function(e) {
this.classList.remove("smoothTransition")
});
element.addEventListener("animationend", function(e) {
this.classList.remove("smoothTransition")
});
You may consider to add your event listeners before transitions.
document.addEventListener("DOMContentLoaded", function(e) {
var element = document.querySelector('.box');
element.addEventListener("webkitAnimationEnd", function(e) {
this.classList.remove("smoothTransition");
console.log('webkitAnimationEnd');
});
element.addEventListener("animationend", function(e) {
this.classList.remove("smoothTransition");
console.log('animationend');
});
element.classList.add("smoothTransition")
element.classList.toggle("fullscreen")
});
.box {
width: 150px;
height: 150px;
background: red;
margin-top: 20px;
margin-left: auto;
margin-right: auto;
}
#keyframes colorchange {
0% { background: yellow }
100% { background: blue }
}
.smoothTransition {
animation: colorchange 2s;
}
.fullscreen {
width: 100%;
height: 100vh;
}
<div class="box"></div>

Keep a second div visible if the mouse is over the first or second div

There are two divs; Div A (display:none by default) and Div B (visible all the time). How would one make it so if mouse moves over Div B, Div A becomes visible. Div A should remain visible if the mouse cursor is on either Div A or Div B, otherwise Div A should be hidden.
I'm using jQuery plugin hoverIntent for this.
$(".the-dropdown").hoverIntent( function(){
$(".the-dropdown").show();
}, function(){
$(".the-dropdown").hide();
});
$(".menu-item").hoverIntent( function(){
$(".the-dropdown").show();
}, function(){
$(".the-dropdown").hide();
});
jsfiddle
Hmm, try something like this.
HTML:
<div id="a"></div>
<div id="b"></div>
CSS:
div {
height: 200px;
width: 200px;
}
#a {
background: #0f0;
display: none;
}
#b {
background: #f0f;
}
JS:
$('#a, #b').hover(function() {
$('#a').show();
}, function() {
$('#a').hide();
});
Fiddle
Or in your specific case:
$(".the-dropdown, .menu-item").hover( function(){
$(".the-dropdown").show();
}, function(){
$(".the-dropdown").hide();
});
hoverIntent is a plug-in that attempts to determine the user's
intent... like a crystal ball, only with mouse movement! It is similar
to jQuery's hover method. However, instead of calling the handlerIn
function immediately, hoverIntent waits until the user's mouse slows
down enough before making the call.
Why? To delay or prevent the accidental firing of animations or ajax
calls. Simple timeouts work for small areas, but if your target area
is large it may execute regardless of intent. That's where hoverIntent
comes in...
If you would like to use the hoverIntent plugin you can download it here:
http://cherne.net/brian/resources/jquery.hoverIntent.html
Working Example Using hoverIntent
$(".menu-item").hoverIntent({
over: function () {
$(".the-dropdown").slideDown();
},
out: function () {
$(".the-dropdown").slideUp();
},
timeout: 500,
interval: 500
});
<div class="menu-item">Hover this for half a second
<div class="the-dropdown"></div>
</div>
div {
height: 200px;
width: 200px;
}
.the-dropdown {
background: red;
display: none;
position:relative;
top:182px;
}
.menu-item {
background: blue;
}

Categories