The following code is causing a memory leak (you can see this happening the more you hover in and out the slower it gets). Unfortunately I am unable to download a javascript profiler in my office (I can, it will just take me a few days/ weeks).
Here is the code, just some simple transitions for a dropdown menu:
$(document).ready(function(){
breadcrumbOver = function () {
$(this).stop().animate({ backgroundColor: "#3393b5", textIndent: 15 }, 250);
}
breadcrumbOut = function () {
$(this).stop().animate({ backgroundColor: "#738793", textIndent: 0 }, 250);
}
$("nav ul li").hover(
function () {
$(this).children('ul.child').stop().slideDown('fast').children('li').hover(breadcrumbOver, breadcrumbOut);
},
function () {
$(this).children('ul.child').stop().slideUp('fast').unbind(breadcrumbOver, breadcrumbOut);
}
);
});
Can anyone see where a memory leak may be occuring?
Edit: Live example here - http://rcnhca.org.uk/sandbox/ (Repeatedly roll over "Health, Safety and Security" then roll over it's children to see the effect happen, also the animation slideDown doesn't fire sometimes if you roll in and out fast enough).
The problem looks like it might be in your initial selector. It targets all li tags under a ul under nav, which includes all children and grandchildren.
$("nav ul li") ...
This causes it to add a hover callback to all li tags under nav, then when you hover it adds yet another hover callback.
You might want to be more specific with it, such as specifically targeting the direct children.
$("nav>ul>li")
If you have children classes, you can also use :not(.child) to target everything that's not a child. Use console.log (which is built into Chrome or using Firebug) to log what those selectors are pulling to make sure you're getting what you expect.
I see what you're talking about George; the effect is most noticeable when swiping between elements rapidly after the box has been opened several times.
Perhaps this will be something that impacts what you're seeing. I read up on stop() and felt like it could have an impact on what you're seeing. The first attribute you could specify in stop() is clearQueue. The second is equally interesting. Here is what the documentation says about it:
clearQueue A boolean indicating whether to remove queued animation as well. Defaults to false.
jumpToEnd A Boolean indicating whether to complete the current animation immediately. Defaults to false.
Might be worth a shot to pass in true like this:
$(this).children('ul.child').stop(true, true)...
// (or you want the animation to unwind, I suppose)
$(this).children('ul.child').stop(true)...
Also, I checked out unbind as well and I don't think you can pass in two events like you are. Perhaps you could use it this way and not experience the problems you are:
...unbind(breadcrumbOver).unbind(breadcrumbOut);
Related
I have a series of spans (togglers) and a series of divs (toggled). I created a make_toggle function that receives the toggler and its corresponding toggled as arguments.
Everything seems to work kind of ok up to the point where I try to implement a "toggle on click out". What I've tried is to attach to the html click event a function that checks whether the target of the click is contained within the toggled element. On toggle "back", I would then detach the handler so I am only checking when I need.
var check_if_clickingout = function(e) {
if (!toggled[0].contains(e.target)) {
toggle();
}
};
See fiddle: https://jsfiddle.net/andinse/65o211nc/11/
It doesn't even seem to work anymore but when it used to, it was triggering many more times than necessary (which was the reason for me to come here ask for help).
What am I doing wrong? What is the most effective way to go about this kind of situation where I am giving functionality to a series of independent DOM elements?
Just putting this out here that this seems to do the same thing.
$("span").click(function() {
$(this).siblings("div").toggleClass("blue");
});
Maybe I am missing something more that I am not seeing in your example.
See more: http://api.jquery.com/toggleclass/
The following code works inconsistently with Chrome but also Firefox (with 'transitionend'). The function slogan_fade_next is just console.log('end');. I always get the classname applied to the first span element but anything after that is hit-and-miss when I click the refresh button, reload, or anything else.
The class of slogan-fadein applied to slogan[] changes the opacity of the element from zero to one but the callback function fade_setup isn't called consistently.
function fade_setup(){
var el = document.getElementsByClassName('slogan')[0];
el = el.getElementsByTagName('span');
for(var i=0;el[i];i++){
el[i].addEventListener('webkitTransitionEnd',slogan_fade_next,false);
}
el[0].className='slogan-fadein';
}
document.addEventListener('DOMContentLoaded', fade_setup);
instead of
document.addEventListener('DOMContentLoaded', fade_setup);
can you use
document.addEventListener('load', fade_setup)
With your current implementation, the JavaScript may be running before the browser has finished applying styles and, therefore, before any transitions are defined.
The problem is caused by a timing issue with applying the styles and anything else as mentioned by Stephen. The problem is, things aren't settled by the time I try to fire the first fade in so I triggered that with window.onload=slogan_fade_next;. Everything has settled in by the time my first element has done its thing.
I've given absolutely no more thought to this other than "it works" and I'm sure I'll come up with a better way to do this.
It sounds simple, and there's a solution involving altering CSS classes, but I was wondering if there's an elegant, on the fly solution to the following issue.
This is a similar question, but it creates "too much recursion" for my scenario.
So I have 2 buttons that both go to the next tab; one at the top of the content, one below. And I want them both to show the state of hover when I hover over either one of them. They have the same html code;
Next ยป
and following the other post, I've tried the solution;
$('a.step-next').on('mousenter mouseleave', function(e) {
$('a.step-next').not(this).trigger(e.type);
});
But my initial logic of using the not() is wrong; it'll just fire the event back and forward between each of the 2 elements.
Clearly, I can alter the CSS to add a new class and remove it on hover/not, which is the route I'll go down. But is there a trigger-only solution?
If i understand correctly you do not want the code-generated event to re-trigger the code..
If so, you can use the extraParameters of .trigger() method
$('a.step-next').on('mousenter mouseleave', function(e, fromCode) {
if (fromCode === undefined){
$('a.step-next').not(this).trigger(e.type, true);
}
});
Not sure what you want to achieve(html required) but this might be the solution you are looking for :
$('a.step-next').on('mousenter mouseleave', function(e) {
$(this).siblings('a.step-next').trigger(e.type);
});
So I'm using YUI to add some animations in my application triggered when a user clicks certain elements. However, I'm running into a common problem that is easily fixed with some poor coding, but I'm looking for a more elegant solution that's less error-prone.
Often, when the user clicks something, a DOM element is animated (using Y.Anim) and I subscribe to that animation's 'end' event to remove the element from the document after its animation has completed. Pretty standard stuff.
However, problems arise when the user decides to spam-click the element that triggers this event. If the element is going to be removed from the DOM when the animation ends, and the user triggers an event handler that fires off ANOTHER animation on the same element, this 2nd animation will eventually cause YUI to spit really nasty errors because the node it was animating on suddenly disappeared from the document. The quickest solution I've found for this is to just set some module/class-level boolean state like 'this.postAnimating' or something, and inside the event handler that triggers the animation, check if this is set to true, and if so, don't do anything. In the 'end' handler for the animation, set this state to false.
This solution is really, really not ideal for many reasons. Another possible solution is to detach the event handler for duration of the animation and re-attach it once the animation is complete. This is definitely a little better, but I still don't like having to do extra bookkeeping that I could easily forget to do if forgetting to do so leads to incomprehensible YUI errors.
What's an elegant and robust way to solve this problem without mucking up a multi-thousand-line Javascript file with bits and pieces of state?
Here's some example code describing the issue and my solution to it.
var popupShowing = false,
someElement = Y.one('...');
someElement.on("click", showPopUp)
var showPopup = function(e) {
if(!popupShowing) {
popupShowing = true;
var a = new Y.Anim({
node: someElement,
duration: 0.2,
...
});
a.on('end', function() {
someElement.remove(true);
popupShowing = false;
});
a.run();
}
}
So if the user clicks "someElement" many times, only one animation will fire. If I didn't use popupShowing as a guard, many animations on the same node would be fired if the user clicked quickly enough, but the subsequent ones would error out because someElement was removed when the first completed.
Have a look at the Transition API. It's more concise, and may very well do what you want out of the box.
someElement.transition({ opacity: 0, duration: 0.2 }, function () { this.remove(); });
// OR
someElement.on('click', function () { this.hide(true, { duration: 0.2 }); });
// OR
someElement.on('click', someElement.hide);
Personally, I haven't used Anim since Transition was added in 3.2.0. Transition uses CSS3 where supported (with hardware acceleration), and falls back to a JS timer for older browsers.
http://developer.yahoo.com/yui/3/examples/transition/transition-view.html
Edit: By popular demand, a YUI way:
myAnim.get('running')
tells you whether an animation is running. To use this you might have to restructure the way you call the event so the animation is in the right scope, for example:
YUI().use('anim', function (Y) {
var someElement = Y.one('...');
var a = new Y.Anim({
node: someElement,
duration: 0.2,
...
});
a.on('end', function() {
someElement.remove(true);
});
someElement.on('click', function() {
if (!a.get('running')) {
a.run();
}
});
});
jsFiddle Example
Previously I had said: I personally like the way jQuery handles this. In jQuery, each element has a queue for animation functions. During animations an "in progress" sentinel is pushed to the front for the duration of the animation so anything that doesn't want to step on an animation peeks at the front of the queue for "in progress" and decides what to do from there, e.g. do nothing, get in line, preempt the current animation, etc.
I don't know enough about YUI to tell you how to implement this, but I find it to be a very elegant solution.
Quick & dirty solution to this particular issue?
Attach your handlers using .once() instead
someElement.once("click", showPopUp)
Also suitable if you need the handler re-attached later, just call that line again when the animation is done. You could also store your state information on the node itself using setData/getData but that is just a panacea to the real problem of state tracking.
Also, +1 to Luke's suggestion to use Transition for DOM property animation, it's grand.
I've made a javascript menu with css and javascript. I've used some mootools (1.11 , the only version i can use).
The script runs on domready, it fetches a dom element, and adds functions (showmenu, hidemenu) on the mouseenter and mouseleave events. The dom element is three levels of nested ul/li`s.
Now I want to add a delay on the menu of 500 ms when one hovers over the menu for the first time, and again when the users leaves the menu (so that the user has half a second time to get back to the menu).
I dont know how to keep track of the events, and cancel them. My knowledge of javascript is not good enough to know where to start. Can anyone give me an example how i should create this? Not really looking for cut and paste code, more pointers in the working of javascript, which native functions i could use, and what is the best way to set something like this up.
Thanx in advance
p.s. Maybe i want to also have a delay (100 ms or so) when the user is already using the menu for the items to show up. Will this be a lot more complex?
perhaps this can give you an idea: http://www.jsfiddle.net/dimitar/stthk/ (extracted it from another menu class I am working on and modded for delay for you as an example)
basically several interesting bits:
options: {
showDelay: 500,
hideDelay: 500
},
defines your delays on mouseover and out.
and then the bind for mouseenter deferred via .delay():
mouseenter: function() {
$clear(_this.timer);
_this.timer = (function() {
this.retrieve("fold").setStyle("display", "block");
}).delay(_this.options.showDelay, this);
},
mouseleave: function() {
$clear(_this.timer);
_this.timer = (function() {
this.retrieve("fold").setStyle("display", "none");
}).delay(_this.options.hideDelay, this);
}
_this.timer is a shared var that handles the deferred function - it gets cleared up on either mouseout or mouseover. if no event that matters takes place within the allotted time period, it will change the display accordingly, else, it will cancel the function.
this is for mootools 1.2.5 btw (storage system + elment delegation) but the principle remains the same for the bits that matter.
The stylish/anal way of doing it would be to fade in/out the menu. You do that with Fx.Morph where you morph the opacity css property and set the complete property to actually remove the div - notice that it's different to make this work in IE5-7.
The more basic/sensible way is to use setTimeout().