jQuery resize(), scroll(): good practice to use variables? - javascript

So my website is experiencing lag when scrolling. I just wanted to ask if it's a good practice to initialize the jQuery objects that you need in $(window).scroll(function () {}?
For example:
$(window).scroll(function () {
$thisContainer = $('.section #container');
// 10 more initializations...
$thisContainer.css(); // something like that...
}
I feel like it wouldn't be a good idea since this function gets called really often every time the user scrolls. And when it is called, those variables would be reinitialized. That as a result would waste a whole lot of memory and time.
Thanks!

In general you should avoid doing anything inside a callback that was fired by the scroll event because the callback will be executed for every pixel that the window is scrolled. However, depending on the application that you're building, some things simply cannot be avoided inside that callback.
Doing a lot of "expensive" manipulations or queries inside the scroll callback can totally freeze the browser and will make your application unusable, so you have to be very careful and performance cautious.
Here are some examples of good practices.
A general example:
Live example: http://jsfiddle.net/tfjyf0a3/
// Since we can get away with querying the elements outside of the callback
// our application will be a lot snappier because we're doing less work for
// every scrolled pixel. Always query DOM elements outside if possible.
var $output = $('#output');
var $window = $(window);
// This function is executed for every scrolled pixel, so we need to
// avoid doing "expensive" queries or changing the DOM in here too.
function changeFontSize(scrollNumber) {
// Setting the fontSize here is unavoidable because our application needs it.
$output.css('fontSize', scrollNumber <= 50 ? 18 : Math.floor(scrollNumber/10));
}
$window.on('scroll', function() {
// Since `$(this)` here would be the window object, it's better to
// just use the previous reference named `$window`.
// Querying the scrolled position here is unavoidable because our
// application needs it.
var currentScroll = $window.scrollTop();
// Saving a reference of the `scrollTop()` value is better when
// we need to re-use its value.
$output.html(currentScroll + 'px');
// We have to be cautious inside this function as well.
changeFontSize(currentScroll);
});
// This is a good practice when you need to guarantee the execution of the function
// when there isn't enough content in the body to cause a scrollbar in the Browser.
//
// The 'scroll' event will fire only when there is a scrollbar in the Browser.
$window.scroll();
Sometimes you will need to do "expensive" DOM manipulations, queries, or even Ajax requests inside the scroll's callback function. For example imagine building an application that implements a pattern known as infinite loading. In this application when the user has reached close to the bottom of the page by scrolling quickly or slowly, you will need to do the following:
Check if the user has scrolled to the bottom.
Check if there are more resources to load.
Load the resources.
Append the new resources to the DOM.
You definitely wouldn't want to execute all the steps above on every scrolled pixel. A very good practice for this situation is to delay the steps above. An example might look like this:
Delayed execution:
Live example: http://jsfiddle.net/35qb1b88/
var $output = $('#output');
var $window = $(window);
var timer;
function changeFontSize(scrollNumber) {
$output.css('fontSize', scrollNumber <= 50 ? 18 : Math.floor(scrollNumber/10));
// ...
// load resources
// append in DOM
// ...etc
}
function scrollHandler() {
var currentScroll = $window.scrollTop();
$output.html(currentScroll + 'px');
changeFontSize(currentScroll);
}
// This callback will be executed for every pixel but it will
// do nothing if we're scrolling too fast because we're clearing
// the timeout. Only when scrolling has stopped and at least 100
// milliseconds have passed will the `scrollHandler` function run.
$window.on('scroll', function() {
timer && window.clearTimeout(timer);
timer = window.setTimeout(scrollHandler, 100);
});
$window.scroll();
The same principles would apply for the resize event too.

Related

Javascript syncronous call with update

I have a function that performs a long task. I would like to create a function that is able to notify the caller of the progress. Ultimately I want to update the UI with the current progress.
Something like this:
function myLongMethod(progressCallback)
{
for(var i = 0 ... )
{
progressCallback(i) ;
}
}
This works but updates on UI are not smooth. Is there a better way? I would prefer something with a jquery Deferred object using deferred.notify(). Any ideas?
Your code is fine. You have got another problem. Javscript always runs on the UI thread. Your operation is blocking this thread (the browser) and you will see some blocking of your browser window.
Luckily there is a workaround implemented in modern browser called web workers. It's simple just call in your main script another script which then get executed:
var w = new Worker("another_script.js");
If your worker is ready you can react on the result by adding a event listner to the worker:
w.onmessage = function(event) {
//do something
}
When you use this pattern, your UI did not block. You can even return data from a web worker and include scripts into it. More details you can find here and here is a good starting tutorial.
Hi you can apply the easing effect to your UI for smoothness and i am giving the following code it may help you
var oldProgress = 0;
var uiUpdater = null;
function updateUI(newProgress){
if(uiUpdater !=null){
// update your ui to the old progress first
window.clearInterval(uiUpdater); // clearing the previous timer
}
var diff = newProgress - oldProgress;
oldProgress = newProgress;
var stepSize = diff/5; // applying the new change in 5 steps to the UI
uiUpdater = window.setInterVal(function(){
// updating your UI after every 100 milliseconds
// to give the smoothness
diff -= stepSize; // decreasing the difference gradually
if(diff<=0){
window.clearInterval(uiUpdater); // clearing the interval once the update is done
}
},100);
}
You have to call the "updateUI" method from you callback with the new progress.

How to interrupt previous event triggers in jQuery

So I've got a scroll event. It does a load of stuff to work out whether something should be moved on the page. When you scroll down, it fires off. If you wheel down, drag, it fires of bazillions and bazillions of times. As you'd expect, perhaps. Here's some simple dummy code to represent the sequence of events.
function scroller() {
// 1. A really expensive calculation that depends on the scroll position
// 2. Another expensive calculation to work out where should be now
// 3. Stop current animations
// 4. Animate an object to new position based on 1 and 2
}
$(window).on('resize' scroller);
Don't get me wrong, it's usually accurate so there isn't so much a concurrency issue. My animations inside the event call .stop() (as part #3) so the latest version is always* the right one but it's eating up a lot of CPU. I'd like to be a responsible developer here, not expecting every user to have a quad core i7.
So to my question... Can I kill off previous calls to my method from a particular event handler? Is there any way I can interfere with this stack of queued/parallel-running "processes" so that when a new one is added to the stack, the old ones are terminated instantly? I'm sure there's a concurrency-minded way of wording this but I can't think of it.
*At least I think that's the case - if the calculations took longer in an earlier run, their animation could be the last one to be called and could cock up the entire run! Hmm. I hadn't thought about that before thinking about it here. Another reason to stop the previous iterations immediately!
You can ensure the event is fired a maximum of once per x milliseconds. E.g.:
(function ($) {
$.fn.delayEvent = function (event, callback, ms) {
var whichjQuery = parseFloat($().jquery, 10)
, bindMethod = whichjQuery > 1.7 ? "on" : "bind"
, timer = 0;
$(this)[bindMethod](event, function (event) {
clearTimeout (timer);
timer = setTimeout($.proxy(callback, this, event), ms);
});
return $(this);
};
})(jQuery);
$(window).delayEvent("resize", scroller, 1000);
Minimalistic demo: http://jsfiddle.net/karim79/z2Qhz/6/

javascript / jquery - getting a callback for the scroller position

I'm trying to get the vertical position of the browser scroll bar with javascript. I am currently using the jquery scroll() function, but I feel like the callbacks are a bit slow, and I'm wondering if there is a faster, native way to use a callback when a user scrolls in the browser. Does anyone know?
If you feel the callback is taking too much time to run, you could throttle it. Just keep track of the time it was last called: http://jsfiddle.net/fjjAw/.
var lastCalled = 0;
$(window).on("scroll", function() {
var now = Date.now();
if(now - lastCalled > 1000) { // only proceed if it has been one second since last call
lastCalled = now;
// do things
}
});
Here is the browser DOM level event to listen to. I recommend using jQuery since it will use this same native event and takes the pain out of crass platform / cross browser issues that can sometimes arise.
window.addEventListener ("scroll", function(){console.log('scrolling');});

run command only at the first occurrence of the window resize

I would like to know if the following situation it's possible. Let's say I have the next $.resize() function:
$(window).resize(function(){
if($(window).width() < 500) console.log('Small');
});
I know that the above will log Small as long as the size of the window is under 500. So this means if you resize your window from 500 to 200, the Small will be logged about 300 times.
Well, that means if you actually have some heavy function running under those circumstances, the browser might crash because it will run over and over again until you stop resizing or the circumstances change.
So that is why I'm asking if there would be a way to run that hypothetical function only at the first occurrence of the condition ?
Use the one event bind which runs the passed in function once only at the first occurrence of the event.
$(window).one("resize", function(){
if($(window).width() < 500) console.log('Small');
});
You can use one event handler in jquery to do it.
http://api.jquery.com/one/
like this
$(window).one('resize', function(){alert('lol');});
This should work
$(window).resize(function(){
// apply whatever you want to do with it, other conditions etc...
// regardless of what you want to do, the following line unbinds the resize event
// from within the handler.. consider calling this once all your conditions are met
$(window).unbind("resize");
});
this handler executes your code, then unbinds the resize event from window
Edit: removed conditions. The purpose of this code is just to show how to call unbind, apply the conditions that are best for your scenario
Edit 2: Addressing one of the comments, unbind the way I presented on the code above, will ultimately unbind all handlers for the resize event. A better approach would be declaring the event handler in a separate method like this:
var Window_Resize = function(e) {
// code goes here
};
$(window).resize(Window_Resize);
and to unbind, use this
$(window).unbind(Window_Resize)
This way you only remove that specific handler. Other handlers would still be bound to this event.
There are two possible solutions. If you want the handler to be run the very first time you resize the window, you can force it to run only once:
$(window).one('resize', function () {
if ($(window).width() < 500) console.log('Small');
});
But then you have a problem: it literally only runs once. What happens if they resize it again to be large?
A solution is to introduce a "tolerance" zone, where you run your code only if the window has been resized within a certain period of time:
var RESIZE_TOLERANCE = 200; // milliseconds
var last_resize = 0;
$(window).resize(function () {
var current_time = (new Date()).getTime();
if (current_time - last_resize < RESIZE_TOLERANCE) {
return; // stop
}
last_resize = current_time;
if ($(window).width() < 500) console.log('Small');
});
This forces the resize handler to run at maximum five times per second. You can change the tolerance if you so desire.
However, think of this situation: we resize down from 999px to 998px, firing the resize handler. Then we resize from 998px to 200px before the 200ms is up. The issue is that we have missed the resize event.
A much better solution is to keep track of the current small state and only execute your heavy code if the state changes:
var RESIZE_TOLERANCE = 100; // milliseconds
var SMALL_TOLERANCE = 500; // pixels
var last_resize = 0;
var small = $(window).width() < SMALL_TOLERANCE;
$(window).resize(function () {
var current_time = (new Date()).getTime();
if (current_time - last_resize < RESIZE_TOLERANCE) {
return; // stop
}
last_resize = current_time;
var is_small = $(window).width() < SMALL_TOLERANCE;
if (is_small !== small) {
// run expensive code here
small = is_small;
}
});
Now the tolerance is 100ms, which means we won't be recalculating the window's width more than that. (You can remove that or change it if you want.) We only run the expensive code if the state of the screen has changed. I don't know if this is what you're looking for, but you will have to be creative if your requirements are subtly different.
The straightfoward answer is:
$(window).resize(function(){
if($(window).width() < 500 && !ranfirst) {
ranfirst = true;
console.log('Small');
}
});
This probably won't work the way you're thinking, though it does satisfy the phrasing of your question. You might find you need to set a timeout to poll the size of the window and unset ranfirst when the size remains the same for 10 seconds or so.

Using setTimeout to improve responsiveness

When looking to improve a page's performance, one technique I haven't heard mentioned before is using setTimeout to prevent javascript from holding up the rendering of a page.
For example, imagine we have a particularly time-consuming piece of jQuery inline with the html:
$('input').click(function () {
// Do stuff
});
If this code is inline, we are holding up the perceived completion of the page while the piece of jquery is busy attaching a click handler to every input on the page.
Would it be wise to spawn a new thread instead:
setTimeout(function() {
$('input').click(function () {
// Do stuff
})
}, 100);
The only downside I can see is that there is now a greater chance the user clicks on an element before the click handler is attached. However, this risk may be acceptable and we have a degree of this risk anyway, even without setTimeout.
Am I right, or am I wrong?
The actual technique is to use setTimeout with a time of 0.
This works because JavaScript is single-threaded. A timeout doesn't cause the browser to spawn another thread, nor does it guarantee that the code will execute in the specified time. However, the code will be executed when both:
The specified time has elapsed.
Execution control is handed back to the browser.
Therefore calling setTimeout with a time of 0 can be considered as temporarily yielding to the browser.
This means if you have long running code, you can simulate multi-threading by regularly yielding with a setTimeout. Your code may look something like this:
var batches = [...]; // Some array
var currentBatch = 0;
// Start long-running code, whenever browser is ready
setTimeout(doBatch, 0);
function doBatch() {
if (currentBatch < batches.length) {
// Do stuff with batches[currentBatch]
currentBatch++;
setTimeout(doBatch, 0);
}
}
Note: While it's useful to know this technique in some scenarios, I highly doubt you will need it in the situation you describe (assigning event handlers on DOM ready). If performance is indeed an issue, I would suggest looking into ways of improving the real performance by tweaking the selector.
For example if you only have one form on the page which contains <input>s, then give the <form> an ID, and use $('#someId input').
setTimeout() can be used to improve the "perceived" load time -- but not the way you've shown it. Using setTimeout() does not cause your code to run in a separate thread. Instead setTimeout() simply yields the thread back to the browser for (approximately) the specified amount of time. When it's time for your function to run, the browser will yield the thread back to the javascript engine. In javascript there is never more than one thread (unless you're using something like "Web Workers").
So, if you want to use setTimeout() to improve performance during a computation-intensive task, you must break that task into smaller chunks, and execute them in-order, chaining them together using setTimeout(). Something like this works well:
function runTasks( tasks, idx ) {
idx = idx || 0;
tasks[idx++]();
if( idx < tasks.length ) {
setTimeout( function(){ runTasks(tasks, idx); },1);
}
}
runTasks([
function() {
/* do first part */
},
function() {
/* do next part */
},
function() {
/* do final part */
}
]);
Note:
The functions are executed in order. There can be as many as you need.
When the first function returns, the next one is called via setTimeout().
The timeout value I've used is 1. This is sufficient to cause a yield, and the browser will take the thread if it needs it, or allow the next task to proceed if there's time. You can experiment with other values if you feel the need, but usually 1 is what you want for these purposes.
You are correct, there is a greater chance of a "missed" click, but with a low timeout value, its pretty unlikely.

Categories

Resources