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');});
Related
Say I have 20 rows of JS code and I want the interpreter to execute only half of the code (<11 rows), then stop, without functions and returns, or without commenting the rest of the code (I already tried a return, see in advance).
A location.reload(true); in row 10 is a close solution but I'm looking for a client side stop.
My question
Is there like a stop command (or functionality) in JavaScript, that asks the interpreter to stop and behave as if no code ran so far?
Why I ask
The background for this question is a problem I have calling a function in more than one keydown event.
Given the keydown event is triggered only once, I consider sending the interpreter back to the start after the keydown event was triggered disposably, and without refreshing the page (Sorry if it seems absurd, I'm new to JS and failed finding the source of the bug).
Of course, the above question is different than the question "why does the keydown event triggered only once", which I already asked here - here's a link for context.
Preventing an XY problem
On one hand, I want to make sure there is no XY problem. On the other hand, I am not allowed to copywrite the previous question to this session hence linked to it above.
Either way, I would be glad to know if what I just described (client side stop of a JS interpreter) is even possible in the current release of the language.
Note: I decided to carefully rewrite the question after some comments earlier today (there were no answers) and did my best ensuring the question is informative and communal.
There is no stop command, but I experienced the need of it before when there was a long-running client-side operation.
The solution:
1) Divide the problem into small packets
2) Make sure you are able to make your function work only for activeMilliseconds milliseconds:
function doStuff(packets, currentIndex, activeMilliseconds) {
var start = new Date(); //Start of chunk
while((currentIndex < packets.length) && (new Date() - start < activeMilliseconds)) {
//Do something with packets[currentIndex]
currentIndex++;
}
return currentIndex;
}
3) Now that we are able to work for activeMilliseconds milliseconds, we need to use this asynchronously:
//Define packets
var currentIndex = 0;
var intervalID = setTimeout(function() {
If(currentIndex = doStuff(packets, currentIndex, activeMilliseconds) >= packets.length) clearInterval(intervalID);
}, totalMilliseconds);
Node: totalMilliseconds > activeMilliseconds should be true. For example, if totalMilliseconds is 250, and activeMilliseconds is 200, then in each 250 milliseconds a chunk will run for 200 milliseconds, leaving the browser to do its stuff for 50 milliseconds every 250 milliseconds even if there is a lot of work to do.
4) Make sure a job stops a previous similar job:
function doJob(packets, intervalID, activeMilliseconds, totalMilliseconds) {
clearInterval(intervalID);
//Define packets
var currentIndex = 0;
var intervalID = setTimeout(function() {
If(currentIndex = doStuff(packets, currentIndex, activeMilliseconds) >= packets.length) clearInterval(intervalID);
return intervalID;
}, totalMilliseconds);
}
If you use this idea for your key event, then it will stop the previous keyboard, your maximum wait time to do so will be activeMilliseconds, which is an acceptable compromise in my opinion.
That said, this methodology should be only used in the case when you have no other option. You need to know that Javascript has a single thread, so even if you trigger a function execution while a previous instance of the event is still running, your new event will sequentially be executed when the other event is finished.
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.
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.
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/
I've got multiple elements on my page that fade in and out on a timer using javascript setInterval to set them in motion. I have them delayed so they are offset just slightly to create a nice cascading effect, but if you leave the page open long enough, they all catch up to one another and the timing gets all messed up (you've got to leave it for a few minutes).
I have an ugly example of the issue at CodePen here: http://www.cdpn.io/wgqJj
Again, you've got to leave the page open and untouched for a few minutes to see the problem. If you had more items on the page (5 or 10) the problem becomes even more apparent. I've also used this type of effect with several jQuery photo rotator plugins, and over time, the issue always crops up.
Is there any explanation for this?
Here is the code I'm using (I know the javascript could be cleaner):
HTML:
<p id="one">First</p>
<p id="two">Second</p>
<p id="three">Third</p>
JavaScript:
$(document).ready(function() {
var timer1 = setTimeout(startOne,1000);
var timer2 = setTimeout(startTwo,2000);
var timer3 = setTimeout(startThree,3000);
});
function startOne () {
setInterval(flashOne,3000);
}
function startTwo () {
setInterval(flashTwo,3000);
}
function startThree () {
setInterval(flashThree,3000);
}
function flashOne () {
$("#one").fadeTo("slow",0.4).fadeTo("slow",1.0);
}
function flashTwo () {
$("#two").fadeTo("slow",0.4).fadeTo("slow",1.0);
}
function flashThree () {
$("#three").fadeTo("slow",0.4).fadeTo("slow",1.0);
}
Question has already been answered here. Quoting from the top rated answer in this topic:
it will wait AT LEAST 1000MS before it executes, it will NOT wait exactly 1000MS.
Giving an actual answer, I'd solve it like this:
$(function(){
setTimeout(flashOne,1000);
});
function flashOne () {
$("#one").fadeTo("slow",0.4).fadeTo("slow",1.0);
setTimeout(flashTwo,1000);
}
function flashTwo () {
$("#two").fadeTo("slow",0.4).fadeTo("slow",1.0);
setTimeout(flashThree,1000);
}
function flashThree () {
$("#three").fadeTo("slow",0.4).fadeTo("slow",1.0);
setTimeout(flashOne,1000);
}
Like this it's not possible for the timers to mess up, as it's always delayed one second after the item before has flashed.
Consider using a chained setInterval instead as this give a guaranteed slot to the browser. Reference this SO post..
Currently you only use setInterval to start the animation. From there jQuery is handling the "oscillations".
Theoretically using a chained set interval should guarantee a slot, to the browser. More importantly, you can hard code the offset into the code at each interval, instead of only once at the beginning.
The setTimeout() and setInterval() functions do not guarantee that your events run exactly on schedule. CPU load, other browser tasks, and similar can and will affect your timers, therefore they are not reliable enough for your use case.
A solution for this would be asynchronous events (promises or similar) or using the event queue that jQuery supplies. That way you could either nest with callbacks, or queue them up and then fire the queue over again once it hits the last item in the queue. The .queue() API documentation page has an example of this.