Suppose I want to use both touch, and mouse (mouse, and touch screen might be available).
There is at least one way to deal.
When the touch fires use preventDefault (first way):
let onMove = event => {
//either touch, or mouse
};
document.body.addEventListener('touchmove', event=>{
//prevent mouse if this event fires
event.preventDefault();
onMove(event);
}, false);
document.body.addEventListener('mousemove', event=>{
onMove(event);
}, false);
What if instead we do this (second way):
let didTouch = false;
let onMove = event => {
//either touch, or mouse
};
document.body.addEventListener('touchmove', event=>{
didTouch = true;
onMove(event);
}, false);
document.body.addEventListener('mousemove', event=>{
if(didTouch){
didTouch = false;
}else{
onMove(event);
}
}, false);
Is the second way viable for handling both touch, and mouse? The first way is recommended, but I'm interested in the possibility of using either way as long as there isn't unforeseen problems.
In my opinion, the second approach is not a good solution because:
Performance: You are handling 2 events (touch and mouse). The first solution prevents mouse event from firing, so your application is doing less event handling.
Complexity: didTouch is adding unnecessary complexity to your code... as a general rule of thumb, if you can achieve the same result by writing less code, the the shorter solution is preferred, unless there is a valid benefit in using the longer solution.
Another potential problem to keep in mind is Mutual Exclusion. JavaScript is Thread Safe and you don't really need to worry about a second event firing and changing your didTouch flag, while you are still processing the first event. But keep in mind that it is not impossible to run into mutex problems with async methods.
I had this problem one time, but with other events, check my solution:
let handler = function (e) {
// do stuff
};
if (isTouchDevice()) {
key.addEventListener('touchstart', handler);
} else {
key.addEventListener('mousedown', handler);
}
function isTouchDevice() {
var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
var mq = function (query) {
return window.matchMedia(query).matches;
};
if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
return true;
}
// include the 'heartz' as a way to have a non matching MQ to help terminate the join
// https://git.io/vznFH
var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
return mq(query);
}
Related
I was confused when reading code of stream.Readable in Node.js.
here is source code:
https://github.com/nodejs/node/blob/master/lib/_stream_readable.js#L778-L799
Readable.prototype.on = function(ev, fn) {
const res = Stream.prototype.on.call(this, ev, fn);
if (ev === 'data') {
// Start flowing on next tick if stream isn't explicitly paused
if (this._readableState.flowing !== false)
this.resume();
} else if (ev === 'readable') {
const state = this._readableState;
if (!state.endEmitted && !state.readableListening) {
state.readableListening = state.needReadable = true;
state.emittedReadable = false;
if (!state.reading) {
process.nextTick(nReadingNextTick, this);
} else if (state.length) {
emitReadable(this);
}
}
}
return res;
};
Obviously, the if statements only handle data and Readable event, but according to the API document, the on method of stream.readable also accept other events such as close , end , error.
So my question is :
According to the source code, how did stream.Readable handle other events except data and readable?
What you are seeing here is an override for the .on() method so that the Readable class can watch what event listeners are being attached and can do something special when someone installs a listener for the data event or the readable event.
The first line of this function:
const res = Stream.prototype.on.call(this, ev, fn);
is where the Readable passes the callback and event name arguments to its parent so that the normal implementation will be run. A Stream implements the EventEmitter interface so calling the super method with Stream.prototype.on.call(this, ev, fn) will give .on() it's expected default behavior.
Then after calling the parent, it checks to see if the event that someone is listening to is the data event or readable event and then implements a little extra functionality when one of those event listeners is attached.
For the data event, it resumes the stream so that it will start flowing if it was paused and if it was set to flowing mode. This is probably because when a Readable is being initially created and configured, if it starts flowing the stream before the data event listener is attached, then data on the stream could be missed. So, it doesn't start flowing until someone is around to listen to data events.
Note, there are potentially lots of over events that occur on the stream and those are all handled by the call to the base class in the first line. What you are seeing here is just some special behavior that the Readable class wants to implement when two specific event listeners are first added. This code does not affect when those events are sent or how they are listened to. It just triggers a little behavior in the Readable state when a listener for one of these events is first attached.
I have this function in my codebase:
let touch = true;
function init() {
let firstMousemoveHasOccured = false;
$(window).on('mousemove.touchdetection', () => {
if (firstMousemoveHasOccured) {
touch = false;
$(window).off('mousemove.touchdetection touchend.touchdetection');
} else {
firstMousemoveHasOccured = true;
}
});
$(window).on('touchend.touchdetection', () => {
touch = true;
$(window).off('mousemove.touchdetection touchend.touchdetection');
});
}
The event mousemove.touchdetection is not a standard event, so where is this coming from?
These are jQuery namespaced events
The first part of the event name mousemove is the event that, when fired calls the callback. The second part touchdetection is meaningless, except it allows you a mechanism to turn off a specific class or group of mousemove events, easily.
$(document).off('mousemove'); //turns off all callbacks attached to the `mousemove` event.
$(document).off('mousemove.touchdetection'); //turns of all callbacks attached to the mousemove event that have been attached with the touchdetection namespace
The purpose of this, as you'll see from reading the API docs is to allow you to easily modify your listeners in your application without affecting listeners attached by third party code.
We found, during a recent upgrade to Google Chrome (version 55+) where pointer events were introduced "with a newer standard that unifies both models: pointer events.", we need to modify how we handle events to be backwards compatible. Our application is written specifically for Chrome.
We have been experimenting this morning, trying figure out which event has been fired (based on Chrome version) so we can handle the event appropriately for all of our application's users. As a test I wrote the following:
$(document).on('mousedown touchstart pointerdown', '.foo', function(event){
console.log( event.type + ": " + event.which );
switch(event.type) {
case 'mousedown':
console.log('moused!');
break;
case 'touchstart':
console.log('touched!');
break;
case 'pointerdown':
console.log('pointed');
break;
};
})
In non-mobile devices both mousedown and pointerdown are registered. In mobile devices all three events are spit out to the console.
CLARIFICATION: What is needed is for none of the other events to fire once the first one is encountered and handled. In other words, if pointerdown fires, for example, then I need to prevent mousedown and touchstart from firing. Subsequent clicks should work as well, so if I get a pointerdown and click again I should get another pointerdown (or whatever event is appropriate for the browser version or device).
I expected break; would cause jQuery to leave the switch(), but that doesn't happen. So I added the .clearQueue() to each case:
$(this).clearQueue();
Once again, I expected that the event would be encountered and handled, the queue would be cleared and I wouldn't have any other events fired.
I was wrong.
I am beginning to feel like I am missing something obvious but I cannot put my finger on it. I have tried .stop(), .finish() as well as combinations of these functions in order to intercept the one function needed (based on browser version) and clear the queue so the rest will not fire.
I thought I understood the JavaScript event queue pretty well, but apparently I am wrong about that too.
Am I missing something obvious? If so, what it is?
I believe that .stop() and .finish() are related to working with an animation queue, not an event queue and .clearQueue() is not related to the event queue either:
The JQuery documentation says:
When used without an argument, .clearQueue() removes the remaining
functions from fx, the standard effects queue. In this way it is
similar to .stop(true). However, while the .stop() method is meant to
be used only with animations, .clearQueue() can also be used to
remove any function that has been added to a generic jQuery queue with
the .queue() method.
But, if you have handled an event and wish to stop others from being raised, you could implement your own handled flag in a closure as a work-around:
Here's an example (executable version here):
// These free variables will be shared in the various callback functions below
var handled = false;
var eventType = null;
$(document).on('mousedown touchstart pointerdown', '.foo', function(event){
// If the current event hasn't been handled or if the current
// has been handled but the event is the same as the previous fired
// event, proceed:
if(!handled || (handled && event.type === eventType)){
// None of the events have been handled yet.
// Determine which has fired and act appropriately:
// Register the current event for comparison later
eventType = event.type;
console.log( eventType + ": " + event.which );
switch(eventType) {
case 'mousedown':
console.log('moused!');
break;
case 'touchstart':
console.log('touched!');
break;
case 'pointerdown':
console.log('pointed');
break;
};
// Flag that one of the events registered earlier has been handled
handled = true;
} else {
// One of the earlier registered events has already been handled
// Prevent the event from performing its native function
// (i.e. cancel the event)
event.preventDefault();
// Don't allow the event to propagate
event.stopPropagation();
}
});
You could just return false at the end, and only the first type supported (and first fired) will be caught by any system.
The break works just fine but it does not affect this issue as these are different events and so fire the handler as many times as the events triggered.
$(document).on('mousedown touchstart pointerdown', '.foo', function(event){
console.log( event.type + ": " + event.which );
switch(event.type) {
case 'mousedown':
console.log('moused!');
break;
case 'touchstart':
console.log('touched!');
break;
case 'pointerdown':
console.log('pointed');
break;
};
// added the following line which forces any subsequent events for the same action and also stop the event from bubbling up the dom
return false;
})
.foo{
padding:50px;
margin:10px;
border:5px double #ccc;
text-align:center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="foo">.foo box for clicking</div>
I can't figure out why smooth scrolling lags when other websites are being loaded simultaneously in other tabs.
For instance, on http://www.feedrover.com/, if you click multiple article links, the site's smooth scroll will lag heavily while the other articles load. This shouldn't be happening, since the javascript for feedrover should be done?
Any ideas on how to fix this?
A solution from MDN:
Since scroll events can fire at a high rate, the event handler shouldn't execute computationally expensive operations such as DOM modifications. Instead, it is recommended to throttle the event using requestAnimationFrame, setTimeout or customEvent, as follows:
(function() {
var throttle = function(type, name, obj) {
var obj = obj || window;
var running = false;
var func = function() {
if (running) { return; }
running = true;
requestAnimationFrame(function() {
obj.dispatchEvent(new CustomEvent(name));
running = false;
});
};
obj.addEventListener(type, func);
};
/* init - you can init any event */
throttle ("scroll", "optimizedScroll");
})();
// handle event
window.addEventListener("optimizedScroll", function() {
console.log("Resource conscious scroll callback!");
});
I would like to know if it is possible to fire an event after the scrolling of a page, when using the scrollbar or mouse-wheel (or with a swipe on a touch device).
Basically, I'd like to detect when the user has stopped scrolling so I can then AJAX-load, rather than loading while scrolling.
It seems that jQuery's .scroll() is firing every time a user scrolls, and it seems clunky to have an event fire all the time. Is there such thing as .onScrollAfter(), synonymous to the .onMouseUp()?
I'd like to know whether this is possible (or if a function already exists) without using a framework, though I would consider one; especially jQuery.
This event does not exist. You can emulate it by using timeouts:
Example (concept code):
(function() {
var timer;
/* Basic "listener" */
function scroll_finish(ev) {
clearTimeout(timer);
timer = setTimeout(scroll_finished, 200, ev);
//200ms. Too small = triggered too fast. Too high = reliable, but slow
}
window.onscroll = scroll_finish; // Or addEventListener, it's just a demo
// Fire "events"
var thingey = [];
function scroll_finished(ev) {
// Function logic
for (var i=0; i<thingey.length; i++) {
thingey[i](ev);
}
}
// Add listener
window.addScrollListener = function(fn) {
if (typeof fn === 'function') {
thingey.push(fn);
} else {
throw TypeError('addScrollListener: First argument must be a function.');
}
}
window.removeScrollListener = function(fn) {
var index = thingey.indexOf(fn);
if (index !== -1) thingey.splice(index, 1);
}
})();
Thought I would add this as an answer even though it's old. The event you are trying to recreate I believe is synonymous to debounce. This is available in underscore.js
debounce_.debounce(function, wait, [immediate])
Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed since the last time it was invoked. Useful for implementing behavior that should only happen after the input has stopped arriving. For example: rendering a preview of a Markdown comment, recalculating a layout after the window has stopped being resized, and so on.
So it will wait after your last execution of the specific event. if you do not want a delay, you can just specify 0. David Walsh has a pretty nice implementation you can include in any project.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Which you can go ahead adding by doing
var myEfficientFn = debounce(function() {
// All the taxing stuff you do
}, 250);
window.addEventListener('scroll', myEfficientFn);
Description
You can use the nice jQuery plugin Special scroll events for jQuery by James Padoley.
Works really great.
Check out the page and this jsFiddle Demonstration (Just scroll ;))
More Information
Special scroll events for jQuery
jsFiddle Demonstration