I'm making a ticker in JavaScript. There is a very simple HTML markup that whose super parent is fed into a function that creates the ticker. The function basically clones .ticker-inner multiple times (recursively) and append one after the another until its parent's width becomes equal or greater than window width.
However if I move these functions from SU object to window scope, they work fine but right now it throws maximum call stack error.
var SU = {
createTicker: function(tickerWrapper) {
var tickers = tickerWrapper.find('.tickers'),
child = tickers.find('.ticker-inner');
SU.buildTickerChildrenClones(tickers, child);
},
buildTickerChildrenClones: function(tickers, child) {
var tickerWidth = parseInt(tickers.outerWidth(), 10);
var windowWidth = jQuery(window).width();
if (tickerWidth + 35 <= windowWidth) {
child.clone().insertAfter(child);
SU.buildTickerChildrenClones(tickers, child);
}
}
}
I guess the tickers variable loses its reference.
In buildTickerChildrenClones method, nowhere you are setting the outerWidth to a lesser value to create an exit condition
So, in this line
var tickerWidth = parseInt(tickers.outerWidth(), 10);
ticketWidth is always going to give the same value in every buildTickerChildrenClones call.
The function basically clones .ticker-inner multiple times
(recursively) and append one after the another until its parent's
width becomes equal or greater than window width.
So, rather than checking the tickers's outerWidth, you need to check its parent's outerwidth
var tickerWidth = parseInt(tickers.parent().outerWidth(), 10);
This should work, as long as tickers are not designed to wrap themselves to next line.
I'm using Javascript & jQuery to build a parallax scroll script that manipulates an image in a figure element using transform:translate3d, and based on the reading I've done (Paul Irish's blog, etc), I've been informed the best solution for this task is to use requestAnimationFrame for performance reasons.
Although I understand how to write Javascript, I'm always finding myself uncertain of how to write good Javascript. In particular, while the code below seems to function correctly and smoothly, I'd like to get a few issues resolved that I'm seeing in Chrome Dev Tools.
$(document).ready(function() {
function parallaxWrapper() {
// Get the viewport dimensions
var viewportDims = determineViewport();
var parallaxImages = [];
var lastKnownScrollTop;
// Foreach figure containing a parallax
$('figure.parallax').each(function() {
// Save information about each parallax image
var parallaxImage = {};
parallaxImage.container = $(this);
parallaxImage.containerHeight = $(this).height();
// The image contained within the figure element
parallaxImage.image = $(this).children('img.lazy');
parallaxImage.offsetY = parallaxImage.container.offset().top;
parallaxImages.push(parallaxImage);
});
$(window).on('scroll', function() {
lastKnownScrollTop = $(window).scrollTop();
});
function animateParallaxImages() {
$.each(parallaxImages, function(index, parallaxImage) {
var speed = 3;
var delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / speed;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
});
window.requestAnimationFrame(animateParallaxImages);
}
animateParallaxImages();
}
parallaxWrapper();
});
Firstly, when I head to the 'Timeline' tab in Chrome Dev Tools, and start recording, even with no actions on the page being performed, the "actions recorded" overlay count continues to climb, at a rate of about ~40 per second.
Secondly, why is an "animation frame fired" executing every ~16ms, even when I am not scrolling or interacting with the page, as shown by the image below?
Thirdly, why is the Used JS Heap increasing in size without me interacting with the page? As shown in the image below. I have eliminated all other scripts that could be causing this.
Can anyone help me with some pointers to fix the above issues, and give me suggestions on how I should improve my code?
(1 & 2 -- same answer) The pattern you are using creates a repeating animating loop which attempts to fire at the same rate as the browser refreshes. That's usually 60 time per second so the activity you're seeing is the loop executing approximately every 1000/60=16ms. If there's no work to do, it still fires every 16ms.
(3) The browser consumes memory as needed for your animations but the browser does not reclaim that memory immediately. Instead it occasionally reclaims any orphaned memory in a process called garbage collection. So your memory consumption should go up for a while and then drop in a big chunk. If it doesn't behave that way, then you have a memory leak.
Edit: I had not seen the answers from #user1455003 and #mpd at the time I wrote this. They answered while I was writing the book below.
requestAnimationFrame is analogous to setTimeout, except the browser wont fire your callback function until it's in a "render" cycle, which typically happens about 60 times per second. setTimeout on the other hand can fire as fast as your CPU can handle if you want it to.
Both requestAnimationFrame and setTimeout have to wait until the next available "tick" (for lack of a better term) until it will run. So, for example, if you use requestAnimationFrame it should run about 60 times per second, but if the browsers frame rate drops to 30fps (because you're trying to rotate a giant PNG with a large box-shadow) your callback function will only fire 30 times per second. Similarly, if you use setTimeout(..., 1000) it should run after 1000 milliseconds. However, if some heavy task causes the CPU to get caught up doing work, your callback won't fire until the CPU has cycles to give. John Resig has a great article on JavaScript timers.
So why not use setTimeout(..., 16) instead of request animation frame? Because your CPU might have plenty of head room while the browser's frame rate has dropped to 30fps. In such a case you would be running calculations 60 times per second and trying to render those changes, but the browser can only handle half that much. Your browser would be in a constant state of catch-up if you do it this way... hence the performance benefits of requestAnimationFrame.
For brevity, I am including all suggested changes in a single example below.
The reason you are seeing the animation frame fired so often is because you have a "recursive" animation function which is constantly firing. If you don't want it firing constantly, you can make sure it only fires while the user is scrolling.
The reason you are seeing the memory usage climb has to do with garbage collection, which is the browsers way of cleaning up stale memory. Every time you define a variable or function, the browser has to allocate a block of memory for that information. Browsers are smart enough to know when you are done using a certain variable or function and free up that memory for reuse - however, it will only collect the garbage when there is enough stale memory worth collecting. I can't see the scale of the memory graph in your screenshot, but if the memory is increasing in kilobyte size amounts, the browser may not clean it up for several minutes. You can minimize the allocation of new memory by reusing variable names and functions. In your example, every animation frame (60x second) defines a new function (used in $.each) and 2 variables (speed and delta). These are easily reusable (see code).
If your memory usage continues to increase ad infinitum, then there is a memory leak problem elsewhere in your code. Grab a beer and start doing research as the code you've posted here is leak-free. The biggest culprit is referencing an object (JS object or DOM node) which then gets deleted and the reference still hangs around. For example, if you bind a click event to a DOM node, delete the node, and never unbind the event handler... there ya go, a memory leak.
$(document).ready(function() {
function parallaxWrapper() {
// Get the viewport dimensions
var $window = $(window),
speed = 3,
viewportDims = determineViewport(),
parallaxImages = [],
isScrolling = false,
scrollingTimer = 0,
lastKnownScrollTop;
// Foreach figure containing a parallax
$('figure.parallax').each(function() {
// The browser should clean up this function and $this variable - no need for reuse
var $this = $(this);
// Save information about each parallax image
parallaxImages.push({
container = $this,
containerHeight: $this.height(),
// The image contained within the figure element
image: $this.children('img.lazy'),
offsetY: $this.offset().top
});
});
// This is a bit overkill and could probably be defined inline below
// I just wanted to illustrate reuse...
function onScrollEnd() {
isScrolling = false;
}
$window.on('scroll', function() {
lastKnownScrollTop = $window.scrollTop();
if( !isScrolling ) {
isScrolling = true;
animateParallaxImages();
}
clearTimeout(scrollingTimer);
scrollingTimer = setTimeout(onScrollEnd, 100);
});
function transformImage (index, parallaxImage) {
parallaxImage.image.css({
'transform': 'translate3d(0,' + (
(
lastKnownScrollTop +
(viewportDims.height - parallaxImage.containerHeight) / 2 -
parallaxImage.offsetY
) / speed
) + 'px,0)'
});
}
function animateParallaxImages() {
$.each(parallaxImages, transformImage);
if (isScrolling) {
window.requestAnimationFrame(animateParallaxImages);
}
}
}
parallaxWrapper();
});
#markE's answer is right on for 1 & 2
(3) Is due to the fact that your animation loop is infinitely recursive:
function animateParallaxImages() {
$.each(parallaxImages, function(index, parallaxImage) {
var speed = 3;
var delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / speed;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
});
window.requestAnimationFrame(animateParallaxImages); //recursing here, but there is no base base
}
animateParallaxImages(); //Kick it off
If you look at the example on MDN:
var start = null;
var element = document.getElementById("SomeElementYouWantToAnimate");
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
element.style.left = Math.min(progress/10, 200) + "px";
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
I would suggest either stopping recursion at some point, or refactor your code so functions/variables aren't being declared in the loop:
var SPEED = 3; //constant so only declare once
var delta; // declare outside of the function to reduce the number of allocations needed
function imageIterator(index, parallaxImage){
delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / SPEED;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
}
function animateParallaxImages() {
$.each(parallaxImages, imageIterator); // you could also change this to a traditional loop for a small performance gain for(...)
window.requestAnimationFrame(animateParallaxImages); //recursing here, but there is no base base
}
animateParallaxImages(); //Kick it off
Try getting rid of the animation loop and putting the scroll changes in the 'scroll' function. This will prevent your script from doing transforms when lastKnownScrollTop is unchanged.
$(window).on('scroll', function() {
lastKnownScrollTop = $(window).scrollTop();
$.each(parallaxImages, function(index, parallaxImage) {
var speed = 3;
var delta = ((lastKnownScrollTop + ((viewportDims.height - parallaxImage.containerHeight) / 2)) - parallaxImage.offsetY) / speed;
parallaxImage.image.css({
'transform': 'translate3d(0,'+ delta +'px,0)'
});
});
});
I am trying to create an animation using a sprite sheet and a for loop to manipulate the background position until it has reached the total number or rows in the sheet. Ideally a reset back to the initial position would be practical, but I cannot even get the animation itself to trigger...
With the current function, no errors occur and the background position in my CSS does not change. I even recorded using Chrome DevTools Timeline and there was nothing either then everything related to my page loading. I have also tried using "background-position-y" as well as a simpler value rather then the math I currently have in place.
This is my function:
$(document).load(function() {
var $height= 324;
var $rows= 34;
for(var i=0; i<$rows; i++){
setTimeout(function() {
$('#selector').css("background-position", "0px ", "0" - ($height*i) + "px");
}, 10);
}
});
I hate to ask a question that is similar to previous issues, but I cannot seem to find another individual attempting sprite sheet animation with a for loop, so I suppose it is it's own problem.
p.s. I didn't include a snippet of my HTML and CSS because it is pretty standard and I don't see how that could be the problem. That being said, I am all ears to any potential thoughts!
I am completely revamping my answer
This issue is that the for() loop is not affected by the setTimeout so the function needs to be written on our own terms, not with a loop
Working Fiddle
Here it is..
var $height= 5;
var $rows= 25;
var i = 1; // Starting Point
(function animateMe(i){
if(i<=$rows){ // Test if var i is less than or equal to number of rows
var newHeight = 0-($height*i)+"px"; // Creat New Height Position
console.log(i); //Testing Purposes - You can Delete
$('#selector').css({"background-position": "0px "+ newHeight}); // Set New Position
i++; // Increment by 1 (For Loop Replacement)
setTimeout(function(){animateMe(i)}, 1000); // Wait 1 Second then Trigger Function
};
})(0);
Here is your solution
First Change
$(document).load() To $(document).ready()
And Change .css Syntex as
$('#selector').css("background-position",'0px '+(0 - ($height*i))+'px');
Here is fiddle Check it ihad implemented it on my recent project http://jsfiddle.net/krunalp1993/7HSFH/
Hope it helps you :)
I would like to trigger some functions according to a position of an element. This element's position changes every tenth second. There is two dozens functions to trigger.
I thought about this pseudo-code :
When element position changes{
Loop through all the coordinates to see if a function can be triggered{
if the current element position matches the function's triggering position
execute the function
}
}
But looping through all possible position each split seconds burdens the browser. So if there is a way to have events to do that.
Is it possible ?
Edit:
After Beetroot-Beetroot comment, I must say that the element that moves only moves on an X abscissa : so just one dimension.
It's much like a horizontal timeline moving from left to right, where some animation happen when a certain year is reached.
However the moving speed can be increased by the user, so fixed time to trigger animation is not an option.
There must be many ways to achieve what you want. The code below exploits jQuery's capability to handle custom events to provide a "loosely-coupled" observer pattern.
$(function() {
//Establish the two dozen functions that will be called.
var functionList = [
function() {...},
function() {...},
function() {...},
...
];
var gridParams = {offset:10, pitch:65};//Example grid parameters. Adjust as necessary.
//Establish a custom event and its handler.
var $myElement = $("#myID").data('lastIndex', -1).on('hasMoved', function() {
$element = $(this);
var pos = $element.position();//Position of the moved element relative to its offset parent.
var index = Math.floor((pos.left - gridParams.offset) / gridParams.pitch);//Example algorithm for converting pos.left to grid index.
if(index !== $element.data('lastIndex')) {//Has latest movement align the element with the next grid cell?
functionList[index](index, $element);//Call the selected function.
$element.data('lastIndex', index);//Remember index so it can be tested mext time.
}
});
});
$(function() {
//(Existing) function that moves the element must trigger the custom 'hasMoved' event after the postition has been changed.
function moveElement() {
...
...
...
myElement.trigger('hasMoved');//loosely coupled 'hasMoved' functionality.
}
var movementInterval = setInterval(moveElement, 100);
});
As you can see, an advantage of loose-coupling is that a function and the code that calls it can be in different scopes - .on('hasMoved', function() {...} and myElement.trigger('hasMoved') are in different $(function(){...}) structures.
If you wanted to add other functions to change the position of myElement (eg first, previous, next, last functions), then, after moving the element, they would each simply need to trigger 'hasMoved' to ensure that the appropriate one of your two dozen functions is called, without needing to worry about scopes.
The only thing you need to ensure is that your two dozen functions are scoped such that they can be called by the custom event handler (ie that they are in the same scope or an outer scope, up to and including the global scope).
I've had to make many assumptions, so the code above will not be 100% correct but hopefully it will provide you with a way ahead.
I'm running a relatively simple function (update a span's innerHTML) on mousemove. The application is a Leaflet map. When the mouse is moving, there is palpable lag when zooming, panning and loading tiles. I only need to update the span about 10-20 times per second at most. (See here for the page in question; the update is for the X/Z indicator in the upper-right corner.)
What's the best way to delay and/or defer mousemove event calls? Is it good enough to skip updating innerHTML? Can I unregister and re-register the event after a timeout?
Modifying the text node of the span will be much more efficient than modifying innerHTML.
function mouseMoveAction(ev) {
span.firstChild.data = new Date.toString();
}
But if text nodes won't fulfill the requirement, and you need innerHTML on mousemove, you can use a threshold in the mousemove handler.
/* Keep CPUs to a minimum. */
var MOUSE_MOVE_THRESHOLD = 25,
lastMouseMoveTime = -1;
function mousemoveCallback(ev) {
var now = +new Date;
if(now - lastMouseMoveTime < MOUSE_MOVE_THRESHOLD)
return;
lastMouseMoveTime = now;
mouseMoveAction(ev);
}
Avoid jQuery, et al; they needlessly make things a lot slower and add a lot more complexity.
Have the mousemove set the innerHTML string to a variable and also use a direct plain DOM mousemove event on the element if feasible. See http://bugs.jquery.com/ticket/4398
!function () {
var buffer = null;
elem.onmousemove = function () {
buffer = value;
};
!function k() {
if (buffer) {
span.innerHTML = buffer;
buffer = null;
}
setTimeout(k, 100);
}();
}();
Now the mousemove event hardly does any work (setting innerHTML is A LOT of work btw) and the span is updated 10 times per second.