Custom Scrolling in Js & jQuery : One scroll event - javascript

I'm currently listening for scrollevents in jQuery, using the following code:
$(window).on('wheel', function(e) {
var delta = e.originalEvent.deltaY;
if (delta > 0) //do something
else //do something else
});
The issue I'm having is that when the user scrolls, lots of wheel events are fired.
I want each time the user scrolls to only process the first scroll event.
I.E.: I only want to process the first wheel event and then stop listening/processing them until the user stops scrolling. At this point I want to start listening again so I can handle the next scroll.
I've seen mousewheel DOMMouseScroll used rather than .on('wheel') but don't know enough about the differences to know if that could help.
Cheers,
James

You can use the jQuery debounce plugin to rate limit your function. The following code will limit the callback to executing once every 250ms.
$(window).scroll($.debounce(250, true, function(){
console.log('Scrolling!');
}));
It's also possible to use the Lodash or Underscore libraries as they also have a debounce function.

Related

JavaScript touchend versus click dilemma

I am working on some javascript UI, and using a lot of touch events like 'touchend' for improved response on touch devices. However, there are some logical issues which are bugging me ...
I have seen that many developers mingle 'touchend' and 'click' in the same event. In many cases it will not hurt, but essentially the function would fire twice on touch devices:
button.on('click touchend', function(event) {
// this fires twice on touch devices
});
It has been suggested that one could detect touch capability, and set the event appropriately for example:
var myEvent = ('ontouchstart' in document.documentElement) ? 'touchend' : 'click';
button.on(myEvent, function(event) {
// this fires only once regardless of device
});
The problem with the above, is that it will break on devices that support both touch and mouse. If the user is currently using mouse on a dual-input device, the 'click' will not fire because only 'touchend' is assigned to the button.
Another solution is to detect the device (e.g. "iOS") and assign an event based on that:
Click event called twice on touchend in iPad.
Of course, the solution in the link above is only for iOS (not Android or other devices), and seems more like a "hack" to solve something quite elementary.
Another solution would be to detect mouse-motion, and combine it with touch-capability to figure out if the user is on mouse or touch. Problem of course being that the user might not be moving the mouse from when you want to detect it ...
The most reliable solution I can think of, is to use a simple debounce function to simply make sure the function only triggers once within a short interval (for example 100ms):
button.on('click touchend', $.debounce(100, function(event) {
// this fires only once on all devices
}));
Am I missing something, or does anyone have any better suggestions?
Edit: I found this link after my post, which suggests a similar solution as the above:
How to bind 'touchstart' and 'click' events but not respond to both?
After a day of research, I figured the best solution is to just stick to click and use https://github.com/ftlabs/fastclick to remove the touch delay. I am not 100% sure this is as efficient as touchend, but not far from at least.
I did figure out a way to disable triggering events twice on touch by using stopPropagation and preventDefault, but this is dodgy as it could interfere with other touch gestures depending on the element where it is applied:
button.on('touchend click', function(event) {
event.stopPropagation();
event.preventDefault();
// this fires once on all devices
});
I was in fact looking for a solution to combine touchstart on some UI elements, but I can't see how that can be combined with click other than the solution above.
This question is answered but maybe needs to be updated.
According to a notice from Google, there will be no 300-350ms delay any more if we include the line below in the <head> element.
<meta name="viewport" content="width=device-width">
That's it! And there will be no difference between click and touch event anymore!
Yes disabling double-tap zoom (and hence the click delay) is usually the best option. And we finally have good advice for doing this that will soon work on all browsers.
If, for some reason, you don't want to do that. You can also use UIEvent.sourceCapabilities.firesTouchEvents to explicitly ignore the redundant click. The polyfill for this does something similar to your debouncing code.
Hello you can implement the following way.
function eventHandler(event, selector) {
event.stopPropagation(); // Stop event bubbling.
event.preventDefault(); // Prevent default behaviour
if (event.type === 'touchend') selector.off('click'); // If event type was touch turn off clicks to prevent phantom clicks.
}
// Implement
$('.class').on('touchend click', function(event) {
eventHandler(event, $(this)); // Handle the event.
// Do somethings...
});
Your debounce function will delay handling of every click for 100 ms:
button.on('click touchend', $.debounce(100, function(event) {
// this is delayed a minimum of 100 ms
}));
Instead, I created a cancelDuplicates function that fires right away, but any subsequent calls within 10 ms will be cancelled:
function cancelDuplicates(fn, threshhold, scope) {
if (typeof threshhold !== 'number') threshhold = 10;
var last = 0;
return function () {
var now = +new Date;
if (now >= last + threshhold) {
last = now;
fn.apply(scope || this, arguments);
}
};
}
Usage:
button.on('click touchend', cancelDuplicates(function(event) {
// This fires right away, and calls within 10 ms after are cancelled.
}));
For me using 'onclick' in the html element itself, worked for both touch and click.
<div onclick="cardClicked(this);">Click or Touch Me</div>

Disabling Mouse Wheel for X Milliseconds with javascript?

I have a Website with RoyalSlider and Mousewheel support. http://www.linus.de/mark/drei.php
Everything works fine, but when i use my macbook (touchpad) the thing is that i fire several mousewheel events at a time when scrolling. so basically i want the script to pause for the time (or a bit less) it takes for one slide to change...
What i would need is a javascript which freezes the mousewheel for x milliseconds each time it's been triggered (after sending 1 or -1 to the slider)...
A Timer with a call back and a flag could work. When you start to scroll you set the flag and not allow the scroll wheel to function, see This Answer on how to disable the scroll wheel. When the timer fires (1 second or so) you reset the flag to let the person scroll again. See This page for how to set up a timer with a call back
I can't give you a full code example since you didn't give any code to us but here's the solution.
When you scroll the mouse, a scroll animation begins. Create a variable somewhere outside the event handler, let's say
var animationInProgress = false;
and set it to true right before the animation begining. Then, this RoyalSlider plugin must have some kind of complete handler (I bet it has - it's paid though) - a parameter where you can put a function to be called when the animation is over. So, you put there a function similar to that:
function() {
animationInProgress = false;
}
The last thing is to check the value of the animationInProgress variable each time you want to run an animation
if (false === animationInProgress) {
//run the animation
}
I hope you get the idea.

How do I detect whether scroll events are fired *only once* like on touch devices?

iOS devices (and likely Android ones) have a different scrolling behavior: The scroll event is only fired once after the entire scroll is done.
How do I detect whether the browser behaves this way?
I could use window.Touch or Modernizr.touch but they don't tell me anything about the scroll behavior, it would be like asking if someone is French to understand whether they like croissants, right? :)
I think you're right about the detection because there will be some devices that will support both touch and mouse behaviors (like Windows 8 tablets), some will only support touch (phones) and some will only support mouse (desktops). Because of that, I don't think you can conclusively say that a device only has one behavior as some could have both.
Assuming that what you're really trying to do is to figure out whether you should respond immediately to every scroll event or whether you should use a short delay to see where the scroll destination ends up, then you could code a hybrid effect that could work well in either case.
var lastScroll = new Date();
var scrollTimer;
window.onscroll = function(e) {
function doScroll(e) {
// your scroll logic here
}
// clear any pending timer
if (scrollTimer) {
clearTimeout(scrollTimer);
scrollTimer = null;
}
var now = new Date();
// see if we are getting repeated scroll events
if (now - lastScroll < 500){
scrollTimer = setTimeout(function() {
scrollTimer = null;
doScroll(e);
}, 1000);
} else {
// last scroll event was awhile ago, so process the first one we get
doScroll(e);
}
lastScroll = now;
};
doScroll() would be your scroll processing logic.
This gets you a hybrid approach. It always fires on the first scroll event that arrives when there hasn't recently been a scroll event. If there are a series of scroll events, then it fires on the first one and then waits until they stop for a second.
There are two numbers that you may want to tweak. The first determines how close scroll events must be to consider them rapid fire from the same user action (current set to 500ms). The second determines how long you wait until you process the current scroll position and assume that the user stopped moving the scrollbar (currently set to 1s).

How to right use Fast buttons in jquerymobile and phonegap?

a need to speed up links with onClick attr in Phonegap app.
I would like to use Fast buttons plugin, that i found here:
https://developers.google.com/mobile/articles/fast_buttons
But i dont know, how to right use this plugin should i add this after pageinit or where?
Maybe is it quite silly question, but if i tried to find some examples, with no luck.
Could You add somebody add put here some example, how solve this problem?
Many thanks.
The touchstart (or touchend) event works great if you know the user won't be scrolling. That's actually the reason click events take so long to resolve on mobile devices, the device is waiting to see if the user is scrolling or clicking.
This will perform quite fast as there is no delay for dispatching this event:
$('#myButton').on('touchstart', function () {
//run click code now
});
You can also use jQuery Mobile's vclick event which attempts to use the native touch events but it's main problem is that you can dispatch multiple events using vclick so you should set a timeout to only allow one click at a time, for example:
var clickOk = true;
$('#myButton').on('vclick', function () {
if (clickOk === true) {
clickOk = false;
setTimeout(function () {
clickOk = true;
}, 350);
//run click code now
}
return false;
});
This will allow the event handler to run only once per 350ms which will take care of the multiple events being dispatched since the second event will be ignored.
I would set these event handlers up in a delegated event handler that runs when a pseudo-page gets initialized:
$(document).on('pageinit', '.ui-page', function () {
//bind "fast-click" event handlers now, use "$(this).find(...)" to only bind to elements of the current pseudo-page
});
Jiri If it's not too late I had to do the same thing for my app and needed to pass parameters to the function. I did it by placing the parameters in the id of the button (separarted by underscores) and using this function which grabs the id from every clicked button with a classname of "clickbutton" and splits it into the individual parameters.
$('.clickbutton').live('click', function(event) {
event.preventDefault();
var id = $(this).attr('id');
var parts = $(this).attr('id').split("_");
var item = parts[0];
var button = parts[1];
var type = parts[2];
console.log(item+button+type);
getItemCondition(item,type);
return false;
});
Still having issues with unresponsiveness from JQM click event though!
What about fastclick ?
FastClick is a simple, easy-to-use library for eliminating the 300ms delay between a physical tap and the firing of a click event on mobile browsers. The aim is to make your application feel less laggy and more responsive while avoiding any interference with your current logic.

Firefox scrollTop problem

I have a problem with Firefox scrollTop value and onscroll event. This works great in IE, Safari and Chrome but Firefox seems to lag.
I tried to update some background position with the onscroll event, but when I take the handle and drag it up and down quickly, Firefox stops updating the scrollTop value and it causes some lag in my app.
You can try this code and look in the Firefox console when dragging the handle and you will see the values something stops the updating :
function SaveScrollLocation () {
console.log(document.documentElement.scrollTop);
}
window.onscroll=SaveScrollLocation ;
Any idea how to make Firefox respond more quickly?
There are two ways to handle this - throttle (execute the function with a set interval) and debounce (execute the function after the specified time has passed since the last call). You'll probably want to use throttling in your situation.
A simplified solution may look something like this (Updated: see it at http://jsfiddle.net/yVVNU/1/):
window.onscroll=catchScroll;
var timeOutId = 0;
var jitterBuffer = 200;
function catchScroll()
{
if (timeOutId) clearTimeout (timeOutId);
timeOutId = setTimeout(function(){SaveScrollLocation()}, jitterBuffer);
}
function SaveScrollLocation () {
console.log(document.documentElement.scrollTop);
alert('scrolled');
}
You can also use this jQuery plugin: http://benalman.com/projects/jquery-throttle-debounce-plugin/
$(window).scrollTop() worked for me
Wouldn't the behavior of dragging the window up and down quickly be considered abnormal?
In my view, I wouldn't want to be saving the state if the user is doing that. I'd rather wait until the window has been in the same spot for at least 250ms before recording it's position. The minor variances in position while the user is slamming the scrollbar up and down are probably not very important to the user, know what I mean?
With a little setTimeout magic, couldn't you sidestep this issue AND make your script a little lighter on the browser UI by not firing the SaveScrollLocation until it clear the scroll location is WORTH saving?
Firefox does not (or did not used to) fire the onscroll event as frequently as the other browsers. see here
Interestingly the scrollTop does update at the correct frequency so you can probably use another event such as mousemove. What i did was something like this :
on first scroll event, start listening to mouse move events - update whatever it is you want to based on the scrollTop which does update correctly. After a short timeout has elapsed after an onscroll, stop listening for mouse move events.
var last = +new Date;
function SaveScrollLocation () {
var now = +new Date;
if (now - last > 50) {
// ...
last = now;
}
}
window.onscroll = SaveScrollLocation ;

Categories