I have a function that should scroll a user back to a search input at the top of the page, and then place focus on it (so the cursor is blinking). For some reason, it seems to apply the focus to the search input first. This creates a very quick jump/spazzy movement of the page to the search bar, jumps back to the bottom, and then scrolls up slowly.
The Javascript:
function goToSearch(){
$('html,body').animate({scrollTop: $('#search').offset().top},'medium');
$('#search').focus()
}
The HTML:
<input type="text" id="search" placeholder="search">
...
Search
I've tried setting .delay() functions to no avail; it seems to always apply the .focus() first. Why is this happening?
"Why is this happening?"
The animation effect is asynchronous. That is, the .animate() function returns immediately after "scheduling" the animation (so to speak) and execution continues immediately with the next statement - in your case, the .focus() statement. The actual animation will take place after the current JS completes.
Fortunately the .animate() method provides an option for you to pass a callback function that will be called when the animation is complete, so you can do the focus within that callback function:
$('html,body').animate({scrollTop: $('#search').offset().top},'medium', function(){
$('#search').focus();
});
You should call the focus function when the animation is complete, like so:
function goToSearch(){
$('html,body').animate({scrollTop: $('#search').offset().top},'medium',function(){
$('#search').focus();
});
}
If you found this question like me, you were probably looking for a CSS related issue coupled with the js focus() and you might think you're out of luck. Well, think again - the good news is that there IS a way to have a callback when a CSS animation or transition ends and then fire your event.
You can make use of jQuery's one method and use either webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend for transitions or webkitAnimationEnd oanimationend msAnimationEnd animationend for animations.
An example:
JS:
var myButton = $('#button'),
myBox = $('#box');
myButton.click(function () {
myBox.addClass('change-size');
myBox.one('webkitAnimationEnd oanimationend msAnimationEnd animationend',
function(e) {
// code to execute after animation ends
myBox.removeClass('change-size');
});
});
CSS:
.box {
width: 100px;
height: 100px;
background: hotpink;
}
#keyframes growBox {
to {
width: 300px;
height: 200px;
}
}
.change-size {
animation: growBox 3s linear 0s 1 normal;
}
The solution is not mine, I just found it after a couple of wasted hours, and I'm posting it in case you find this question first.
Source: http://blog.teamtreehouse.com/using-jquery-to-detect-when-css3-animations-and-transitions-end
Related
So, I had an animation function. It worked. You hover over some squares, they leave a little trail and then fade back:
$(".square").bind("webkitAnimationEnd mozAnimationEnd animationend", function(){
$(this).removeClass("squareAnim");
});
$(".square").hover(function(){
$(this).addClass("squareAnim");
});
.squareAnim {
animation-name: changeColor;
animation-duration: 4s;
}
#keyframes changeColor {
0% {background-color: rgb(5, 5, 5);}
25% {background-color: rgba(255, 5, 20);}
50% {background-color: rgba(20, 5, 255);}
100% {background-color: rgb(5, 5, 5);}
}
Later, I decided I needed to add/remove squares based on the page size. I got help here.
Welp, I had to change the animation function because it's bind and won't work on new divs. I researched and tried different functions, and this is where I'm at now:
$(document).ready(function(){
$(document).on("webkitAnimationEnd mozAnimationEnd animationend", ".square", function(){
$(this).removeClass("squareAnim");
});
$(document).on("hover", ".square", function(){
$(this).addClass("squareAnim");
});
});
But, it's not working. I don't know if $(document).ready is redundant (it was my attempt at delegation) but it still doesn't work with or without it. Is there something I'm missing?
Edit jsfiddle
You have to rebind the events when you create the elements (i.e. onresize). It will not propagate to newly created elements if you just bind it once onready.
Also, onhover is deprecated (removed in jQuery 1.9) and should be replaced with onmouseenter and onmouseleave.
From jQuery's docs:
Deprecated in jQuery 1.8, removed in 1.9: The name "hover" used as a shorthand for the string "mouseenter mouseleave". It attaches a single event handler for those two events, and the handler must examine event.type to determine whether the event is mouseenter or mouseleave. Do not confuse the "hover" pseudo-event-name with the .hover() method, which accepts one or two functions.
$(window).on("resize", function() {
multiplyNode(contain.querySelector('.square'), canAdd(), false);
$(document).on("webkitAnimationEnd mozAnimationEnd animationend", ".square", function() {
$(this).removeClass("squareAnim");
});
$(document).on("mouseenter", ".square", function() {
$(this).addClass("squareAnim");
});
}).resize();
Updated fiddle
I am trying to make a kind of template, for a dashboard page, where when a dashboard button is clicked, something is added to the DOM.
I was trying to template this, so that when someone makes a new dashboard for example, he has the option, to specify in CSS an animation that should run on each button when clicked. If an animation is defined on the button, the actual loading of the element should be delayed until the animation completes.
Now, if I actually specify an animation in css, everything works fine, because I am delaying the logical code with a callback on the animations end. My problem is, that I can't achieve the same, when there is no animation set to the element.
What I would want is something simmilar:
function buttonClick($button) {
$button.addClass('activated');
$button.one('animationend', function() {
// ... run the logic here
});
if (...no animation specified in activated class) {
// ... run the logic here
}
}
NOTE: I am using jQuery here, if there is a method specific in jQuery for this, that would also be okay for me, but a plain javascript method would be fine as well. I heard about the jQuery(":animated") selector, but when I was testing it, it seems that it only works for animations started with jQuery itself, and not with CSS3.
As you seem to use animation CSS for your animations (given that you use the animationend event), you could use getComputedStyle to verify the content of the animation-name CSS property.
Here is a demo with two buttons: one triggers an animation on click, while the other doesn't:
$("button").click(function () {
buttonClick($(this));
});
function hasAnimation($button) {
return getComputedStyle($button[0], null)["animation-name"] != "none";
}
function onEndAnimation($button) {
$button.removeClass('activated');
console.log("animation complete on button " + $button.text());
}
function buttonClick($button) {
$button.addClass('activated');
$button.one('animationend', () => onEndAnimation($button));
if (!hasAnimation($button)) onEndAnimation($button);
}
#yes.activated {
animation-duration: 1s;
animation-name: grow;
}
#keyframes grow {
from { width: 50px; }
50% { width: 100px; }
to { width: 50px; }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="yes" style="width: 50px">Yes</button><br>
<button id = "no" style="width: 50px">No</button>
I have a CSS animation that gets applied to a HTML element with a CSS class. I also have an event listener attached to the animationend (it's a modern Windows app so the browser is IE11 only). What I'm seeing is that sometimes the event gets fired and sometimes it doesn't. Regardless of the event firing I can always see it visually animating. It looks like some kind of race condition to me.
I have searched the web trying to understand what could cause this event to not get fired but I haven't found any satisfying results. I found the following on MDN:
Note: The transitionend event doesn't fire if the transition is aborted because the animating property's value is changed before the transition is completed.
UPDATE1: transitionend has nothing to do with animationend so this info is unrelated.
I'm not sure if the same is applicable for CSS animations. Does anyone have any ideas as to what can cause this? If there was any event that could detect that an animation was aborted that could also be a useful workaround.
UPDATE2: The current workaround that I'm using:
element.addEventListener("animationstart", function () {
setTimeout(function () {
// do stuff
}, animationDuration);
});
Is it possible that your animations aren't ending?
Check the documentation for the animationcancel event:
https://developer.mozilla.org/en-US/docs/Web/Events/animationcancel
However, it looks like browser support may be spotty ("onanimationcancel" in HTMLElement.prototype returns false for me in Chrome 67).
It seems that using a setTimeout hack may be necessary for the time being.
You can use this function
function onAnimationEnd(element, handler) {
//- Create local variables
var events = 'animationend transitionend webkitAnimationEnd oanimationend MSAnimationEnd webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd';
//- Bind event to element
$(element).on(events, handler)
//- Return received element
return element
}
This function binds an event when both animation or transition ends.
Also make sure that the animated element is not being deleted or moved while the animation is running.
With IE11, you can't necessarily assume that the JS event binding will occur before the CSS animation starts or ends. This type of issue is intermittent, and frequency/onset will depend on certain factors.
If the CSS animation-duration is low and the animation starts immediately after the stylesheet is ready, then it's possible that the JS event binding will not be applied in time
A solid workaround I've found is to use JS to invoke the CSS animation using a className. i.e., Only apply the CSS animation-name property when the className has been applied via JS.
FWIW, this is still a problem. In some cases (so far I've only reproduced it on mobile) certain elements with a well-defined animation don't always fire the animationend event.
EDIT: Turns out the issue is with animating after content. Safari just doesn't reliably handle it. I ended up making a distinct strikethrough div and animating that.
function animateCSS(element, animationName, callback) {
const node = typeof element == 'string' ? document.querySelector(element) : element;
node.classList.add('animated', animationName);
node.onanimationend = handleAnimationEnd;
console.log('animateCSS', node, animationName);
function handleAnimationEnd() {
console.log('handleAnimationEnd', node, 'remove:', animationName);
node.classList.remove('animated', animationName);
node.onanimationend = null;
if (typeof callback === 'function') callback(node);
}
}
.strike {
color: #e402b3;
position: relative;
}
.strike:after {
content: ' ';
position: absolute;
top: 50%;
left: 2%;
height: 2px;
background: #e402b3;
animation: strike 0.7s linear 0s 1 forwards;
-webkit-animation: strike 0.7s linear 0s 1 forwards;
}
#keyframes strike {
0% {
width: 0%;
color: #000;
}
100% {
width: 96%;
color: #e402b3;
}
}
#-webkit-keyframes strike {
0% {
width: 0%;
color: #000;
}
100% {
width: 96%;
color: #e402b3;
}
}
You can see the issue at http://bmuller.com/index_new.html. Clicking on a location name brings up a home "button" in the bottom left. Clicking that home button returns to the original page load layout, and is supposed to hide the home button. However, as soon as the cursor is moved off the home button, it reappears and returns to its hover behavior (separately defined).
html:
<div id="home">Home</div>
JS:
function nav_click() {
$('.navitem').click(function(){
...
$('#home').fadeTo(200,0.5);
...
});
}
$(document).ready(function(){
nav_click();
$('#home').hover(
function() {
$('#home').fadeTo(100,1)
},
function() {
$('#home').fadeTo(100,0.5)
}
);
$('#home').click(function(){
$('html').removeAttr('style');
$('#navbar').removeAttr('style');
$('.navdate').removeAttr('style');
$('.navitem').fadeIn(200);
$('#pagebrand').fadeIn(200);
$('.arrow').removeAttr('style');
$('#home').hide();
nav_hover();
nav_click();
});
});
Let me know if you need to see more code to answer this. And feel free to tell me why anything else looks wrong/dumb too.
Thanks!
If you put a breakpoint in the last part of #home.click(), you see that it is hidden. Before continuing, you can move the mouse outside the screen and the button is hidden. Put another breakpoint near $('#home').fadeTo(100,0.5)} and you see it gets invoked when your mouse hovers the page, which will thus automatically make the home button appear. Inspecting jQuery it appears to be on mouseout, probably part of the hover mechanism.
As suggested in the comments, use more CSS instead of JS.
See if this gets you started:
#home, .navitem {
cursor: pointer;
}
#home {
opacity: 0.5;
-webkit-transition: opacity linear 100ms;
}
#home:hover {
opacity: 1;
}
#navbar.docked {
top: auto;
left: 0px;
....
}
JS
function attach_nav() {
$('.navitem').click(function(){
$('#navbar').addClass('docked');
});
First of all I think you could do the hover thing via css.
If you want to keep the jquery hover function then you have to unbind the mouseleave event within your click event or simply add a 'clicked' class to the element.
For the 2nd approach I made a little jsfiddy
Making the home button visible by clicking on .navitem (in fiddle called .clickme)
$('.clickme').on('click', function (e) {
e.preventDefault();
$('.menu').removeClass('clicked').show();
});
On click hide the menu again and add a class 'clicked'
$('.menu').on('click', function (e) {
e.preventDefault();
$(this).attr('style', '');
$(this).addClass('clicked').hide();
});
On hover check if its clicked:
if (!$(this).hasClass('clicked'))...
This code below demonstrates how I'm applying CSS transitions to the height property. My understanding of CSS transitions (which is supported by what I'm seeing) is that they are asynchronous. Can anyone tell me how to make CSS transitioning synchronous? I specifically want to use CSS transitions (i.e. not jQuery animations or some other method).
CSS:
.animated {
-webkit-transform-style: preserve-3d;
-webkit-transform: translate3d(0px,0px,0px);
transition: height 1.5s;
-webkit-transition: height 1.5s;
-webkit-perspective: 1500;
-webkit-backface-visibility: hidden;
}
HTML:
<div id='someElement' class='animated'></div>
JavaScript:
$("#someElement").css("height","200px");
//More javascript code I'd like to execute after the css animation has completed
Well, there isn't really a way to make it synchronous, but you can wait for the animation to end, effectively simulating a synchronous behaviour
The solution
var crossBrowserEvent = 'webkitAnimationEnd oanimationend msAnimationEnd animationend';
$("#someElement").css("height","200px");
$("#someElement").one('crossBrowserEvent',function(e) {
do_some();
magic();
here();
when_the_animation_ends();
});
Some additional comments
Because the css3 animations are not yet fully cross browser, you must listen to more then one event.
Some problems could arise if some of the browsers implement the new animationEnd event and also for example the webkitAnimationEnd at the same time. That's why I put the one event attacher instead of the on.
Here is a code that uses pure js and the webkit prefix to create sequential based class changes.
In the DEMO i use more than just one animation so the handler function is executed many times. Thats why i check for the propertyName('color' in my case). If the color animation ends the handler function changes the className with classList.toggle('active');
Each class has different transition lengths.
function handler(e){
//here is when the transition ends.
// add your code here or:
if(e.propertyName=='color'){
e.target.classList.toggle('active');
}
}
var div=document.getElementsByTagName('div')[0];
div.addEventListener('webkitTransitionEnd',handler,false);
div.classList.toggle('active');
DEMO (chrome safari android ios)
http://jsfiddle.net/jaqT7/
if you want more support search for the various prefixes.-webkit,-ms,-moz
and also the correct transitionend handler.
And here is an example using sequential animations using simple delay's
https://stackoverflow.com/a/20778358/2450730