I want to get and keep only the original target element of a mousedown event -- but as the user holds the mouse down, the event.target changes depending on what's under the pointer.
There is a property event.originalTarget which does exactly what I want, but it's only supported by Firefox (reference: https://developer.mozilla.org/en-US/docs/Web/API/Event/originalTarget ).
How can I replicate this behavior using standard event.target?
Figured it out: you need to create flags outside the event listener.
let el = false
let mousedown = false;
document.addEventListener('mousedown', ev => {
if (!mousedown) {
el = ev.target;
mousedown = true;
}
});
document.addEventListener('mouseup', ev => {
mousedown = false;
el = false;
});
So el will be set as the original target for the duration of the mousedown, but on mouseup, it's reset back to nothing.
I want avoid that double click also fire a single click event.
A simple solution i found is to delay the click with a timer and destroy the timer if a double click is fired.
var pendingClick;
function myclick(){
clearTimeout(pendingClick);
pendingClick = setTimeout(function(){
console.log('click');
}, 500);
}
function mydblclick(){
clearTimeout(pendingClick);
console.log('double click');
}
<div onclick="myclick()" ondblclick="mydblclick()">Double Click Me!</div>
But this solution is based on timing, if the double click is too slow (>500ms) it also fire a single click.
There is a stable solution for handle both click and double click?
Double-clicking in itself is "based on timing", even in the standard implementation of dblclick / ondblclick. There will always be the issue of a single-click being fired if the double-click is "too slow". What is "too slow"? 300ms? 500ms? 1000ms? Your double-clicks may be only 50ms apart, while my mom's double-clicks are 1-2 seconds apart...
You can get the event and cancel it with the addEventListener like this:
document.addEventListener('dblclick', (event) => {
event.preventDefault();
event.stopPropagation();
}, true); // With this true, you are cancelling the dblclick event
let pendingClick;
function myclick(){
clearTimeout(pendingClick);
pendingClick = setTimeout(function (){
console.log('click');
}, 500);
}
function mydblclick(){
clearTimeout(pendingClick);
console.log('double click');
}
<div onclick="myclick()" ondblclick="mydblclick()">Double Click Me!</div>
Only work with the 'onclick' function to check if it was one or two clicks and use a variable to count the number of clicks in a given time interval.
Example:
var pendingClick;
var clicked = 0;
var time_dbclick = 500 // 500ms
function myclick(){
clicked++;
if(clicked >= 2){
mydblclick()
clearTimeout(pendingClick)
clicked = 0;
return;
}
clearTimeout(pendingClick)
pendingClick = setTimeout(() => {
console.log('One click!')
clicked = 0;
}, time_dbclick);
}
function mydblclick(){
console.log('double click');
}
<div onclick="myclick()">Double Click Me!</div>
Custom Events instead of inline event handlers
If one prefers to use .addEventListener and .removeEventListener instead of HTML inline-eventhandlers, I would suggest another approach based on Custom Events. That means one would not make use of the standard implementation of "click" and "dblclick", but create own event handling for both:
let lastLeftClick = document.dispatchEvent(new Event("click"));
let doubleclickLength = 300;
function leftClickHandler (e) {
if (e.button != 0) return; // only left clicks shall be handled;
let delaySinceLastClick = e.timeStamp - lastLeftClick.timeStamp;
let eIsDoubleClick = delaySinceLastClick < doubleclickLength;
if (eIsDoubleClick) {
let doubleclickEvt = new CustomEvent("doubleclick", e);
lastLeftClick = lastLeftClick = doubleclickEvt;
document.dispatchEvent(doubleclickEvt);
} else {
let singleClickEvt = new CustomEvent("singleclick", e);
lastLeftClick = singleClickEvt;
document.dispatchEvent(lastLeftClick);
}
}
// adding above click event implementation:
document.addEventListener("click", leftClickHandler);
using the new custom events:
document.addEventListener("singleclick", e=>console.log("single click"));
document.addEventListener("doubleclick", e=>console.log("double click"));
I'm trying to create a horizontal scrolling container. In a precise case i need to revert e.preventDefault(); from a click.
I tried a lot of options, changing 'window.location.href' in the else statement seems to be a great option.
But i can't figure how to grab the href from the link clicked.
Any idea can help to achieve my goal. :)
slider.addEventListener('mouseup', () => {
isDown = false;
// Disable click event (for ever unfortunately)
if(moved === true) {
this.addEventListener('click', (e) => {
e.preventDefault();
});
} else {
// trying to reset click function
}
You can conditionally prevent a click event from firing on your slider by registering a click event listener that shares the moved variable with your mousedown and mousemove event listeners.
The { passive: true } option indicates that the listener does not call event.preventDefault(), and saves a lot CPU time particularly for the mousemove event which can fire several times per second.
The true parameter indicates that the event listener should be called before the event starts to bubble up from the target element. This allows it to prevent propagation even to listeners that were already added on the same element, as long as they didn't also set useCapture to true.
const slider = document.querySelector('input[type="range"]');
// prevent this if mousemove occurred between mousedown and mouseup
slider.addEventListener('click', () => {
console.log('click event fired on slider');
});
// fires just before click event
slider.addEventListener('mouseup', () => {
console.log('mouseup event fired on slider');
});
let moved = false;
// reset for each potential click
slider.addEventListener('mousedown', () => {
moved = false;
});
// indicate cancellation should occur for click
slider.addEventListener('mousemove', () => {
moved = true;
}, { passive: true });
// prevents click event if mousemove occurred between mousedown and mouseup
slider.addEventListener('click', event => {
if (moved) {
event.preventDefault();
event.stopImmediatePropagation();
}
}, true);
<input type="range" />
You should remove the event listener containing the event.preventDefault();.
In order to do that you have to save your function reference into a variable like so:
const preventClickHandler = (e) => e.preventDefault;
slider.addEventListener('mouseup', () => {
isDown = false;
// Disable click event (for ever unfortunately)
if(moved === true) {
this.addEventListener('click', preventClickHandler);
} else {
this.removeEventListener('click', preventClickHandler);
}
})
I am trying to catch when user press left button on mouse while hovering over cells in a html table using vanilla javascript. The purpose is to paint a cell in black when user is clicking with mouse while dragging (drawing like in MsPaint, when you draw a line for example)
I added an "over" event listener on each td of my table and used buttons property to check if left button is pressed or not:
celle = document.getElementsByTagName("td");
for (i=0;i<celle.length;i++)
celle[i].addEventListener("mouseover", function(e){
if(e.buttons == 1 ){
e.target.style.backgroundColor="black";
}
})
This code works but not always and not perfectly. First it starts setting the background color of the next element, not the one on which I pressed the mouse. Moreover, sometimes it doesn't set any color at all (there is a small icon like "accessed denied" in Chrome's window). It appears to work quite randomly and unpredicatably.
I tried also with jQuery, but I found similar problems. Anyone can help me?
Thanks a lot
Split the problem into several parts. I would add a mousedown and mouseup eventlistener to the whole window and set a global state if you're currently drawing:
var drawState=false
window.addEventListener("mousedown",function(e){
if(e.button===1){
drawState = true;
}});
window.addEventListener("mouseup",function(e){
if(e.button===1){
drawState = false;
}});
You can improve the window listeners with some checks, if the mouse is over a cell.
After this you can add a mouseenter listener to all your cells. Mouseenter is only fired once you enter a cell and not on every move inside the element:
celle[i].addEventListener("mouseenter", function(e){
if(drawState){
e.target.style.backgroundColor="black";
}
})
Instead of tracking mouseover, track three events:
mousemove - to constantly get the mouse position
mousedown - to set the mouse state as currently clicked down
mouseup - to set the mouse state as currently released
It works this way:
handleMousemove constantly updates the mouse position and check mouse state
When the mouse is clicked down, handleMousedown is fired
handleMousedown set the state as 'down'
When handleMousemove sees that mouse state is 'down', it fires click event at the current mouse position
When the mouse is released, handleMouseup is fired
handleMouseup set the state as 'released' and everything returns to normal
Repeat
var mouseIsDown = false;
var mousePosition = { x:-1, y:-1 };
let handleMousemove = (event) => {
// get the mouse position
mousePosition.x = event.x;
mousePosition.y = event.y;
if(mouseIsDown) // if mouse state is currently down, fire click at mouse position
{
let elem = document.elementFromPoint(mousePosition.x, mousePosition.y);
// you can add some conditions before clicking
if(something)
{
elem.click();
}
}
};
let handleMousedown = (event) => {
mouseIsDown = true;
// set the mouse state as 'down'
};
let handleMouseup = (event) => {
mouseIsDown = false;
// set the mouse state as 'release'
};
document.addEventListener('mousemove', handleMousemove);
document.addEventListener('mousedown', handleMousedown);
document.addEventListener('mouseup', handleMouseup);
Working fiddle: https://jsfiddle.net/Black3800/9wvh8bzg/5/
Thanks to everybody for your kind answers. Proposed codes work almost ok. The only problem is that sometimes browser shows the NO SYMBOL cursor. Unfortunately I can't post an image but you can find it here:
NO Symbol
and the only way to keep on drawing is clicking outside the table and then clicking again inside.
This is my code:
var mouseIsDown = false;
var mousePosition = { x:-1, y:-1 };
let handleMousemove = (event) => {
// get the mouse position
mousePosition.x = event.x;
mousePosition.y = event.y;
if(mouseIsDown) // if mouse state is currently down, fire click at mouse position
{
let elem = document.elementFromPoint(mousePosition.x, mousePosition.y);
// you can add some conditions before clicking
if (event.buttons==1)
{
elem.click();
}
}
};
let handleMousedown = (event) => {
mouseIsDown = true;
// set the mouse state as 'down'
};
let handleMouseup = (event) => {
mouseIsDown = false;
// set the mouse state as 'release'
};
document.addEventListener('mousemove', handleMousemove);
document.addEventListener('mousedown', handleMousedown);
document.addEventListener('mouseup', handleMouseup);
celle = document.getElementsByTagName("td");
for (i=0;i<celle.length;i++)
celle[i].addEventListener("click", function(e){
e.target.style.backgroundColor="black";
}
)
Isn't it easier to just add a listener for "click" ? If the element is clicked it also over the cell.
celle[i].addEventListener("click", function(e){
e.target.style.backgroundColor="black";
}
First of all, here is a list of event types that are defined by the W3C standards. (This list is based on the onevent attributes defined in the HTML5 standard. I assume that there are dozens of other event types, but this list is long enough as it is.)
abort
afterprint
beforeprint
beforeunload
blur
canplay
canplaythrough
change
click
contextmenu
copy
cuechange
cut
dblclick
DOMContentLoaded
drag
dragend
dragenter
dragleave
dragover
dragstart
drop
durationchange
emptied
ended
error
focus
focusin
focusout
formchange
forminput
hashchange
input
invalid
keydown
keypress
keyup
load
loadeddata
loadedmetadata
loadstart
message
mousedown
mouseenter
mouseleave
mousemove
mouseout
mouseover
mouseup
mousewheel
offline
online
pagehide
pageshow
paste
pause
play
playing
popstate
progress
ratechange
readystatechange
redo
reset
resize
scroll
seeked
seeking
select
show
stalled
storage
submit
suspend
timeupdate
undo
unload
volumechange
waiting
Now, is it possible to define a global event handler that is called when any event originally occurs on any element on the page? (In this case, I don't want to count those events that occurred on elements because they bubbled up from a descendant element - that's why I wrote "originally occurs".)
If that is not possible, is it at least possible to define an event handler that is called when any event bubbles up to the root of the DOM tree (which is either the document object or the window object - both should work)? (I know that it's possible to stop bubbling programmatically, but I would use this event handler on a page that has no other handlers defined on any other elements.) (Also, I believe some events don't bubble up, but let's ignore these cases for the sake of this argument.)
I know that I can do this (using jQuery):
$(document).bind('abort afterprint beforeprint beforeunload etc.', function() {
// handle event
});
but that would be a rather undesirable solution for me.
btw I don't need a cross-browser solution. If it works in just one browser, I'm fine.
Also, Firebug is able to log events, but I would like to be able to catch the event programmatically (via JavaScript) rather then having them simply logged in the console.
/*
function getAllEventTypes(){
if(location.href !='https://developer.mozilla.org/en-US/docs/Web/Events') return;
var types = {};
$('.standard-table:eq(0) tr').find('td:eq(1)').map(function(){
var type = $.trim(this.innerText) || 'OtherEvent';
types[type] = types[type] || [];
var event = $.trim(this.previousElementSibling.innerText);
if(event) types[type].push(event);
});
for(var t in types) types[t] = types[t].join(' ');
return "var DOMEvents = "+JSON.stringify(types, null, 4).replace(/"(\w+)\":/ig, '$1:');
}
*/
var DOMEvents = {
UIEvent: "abort DOMActivate error load resize scroll select unload",
ProgressEvent: "abort error load loadend loadstart progress progress timeout",
Event: "abort afterprint beforeprint cached canplay canplaythrough change chargingchange chargingtimechange checking close dischargingtimechange DOMContentLoaded downloading durationchange emptied ended ended error error error error fullscreenchange fullscreenerror input invalid languagechange levelchange loadeddata loadedmetadata noupdate obsolete offline online open open orientationchange pause pointerlockchange pointerlockerror play playing ratechange readystatechange reset seeked seeking stalled submit success suspend timeupdate updateready visibilitychange volumechange waiting",
AnimationEvent: "animationend animationiteration animationstart",
AudioProcessingEvent: "audioprocess",
BeforeUnloadEvent: "beforeunload",
TimeEvent: "beginEvent endEvent repeatEvent",
OtherEvent: "blocked complete upgradeneeded versionchange",
FocusEvent: "blur DOMFocusIn Unimplemented DOMFocusOut Unimplemented focus focusin focusout",
MouseEvent: "click contextmenu dblclick mousedown mouseenter mouseleave mousemove mouseout mouseover mouseup show",
SensorEvent: "compassneedscalibration Unimplemented userproximity",
OfflineAudioCompletionEvent: "complete",
CompositionEvent: "compositionend compositionstart compositionupdate",
ClipboardEvent: "copy cut paste",
DeviceLightEvent: "devicelight",
DeviceMotionEvent: "devicemotion",
DeviceOrientationEvent: "deviceorientation",
DeviceProximityEvent: "deviceproximity",
MutationNameEvent: "DOMAttributeNameChanged DOMElementNameChanged",
MutationEvent: "DOMAttrModified DOMCharacterDataModified DOMNodeInserted DOMNodeInsertedIntoDocument DOMNodeRemoved DOMNodeRemovedFromDocument DOMSubtreeModified",
DragEvent: "drag dragend dragenter dragleave dragover dragstart drop",
GamepadEvent: "gamepadconnected gamepaddisconnected",
HashChangeEvent: "hashchange",
KeyboardEvent: "keydown keypress keyup",
MessageEvent: "message message message message",
PageTransitionEvent: "pagehide pageshow",
PopStateEvent: "popstate",
StorageEvent: "storage",
SVGEvent: "SVGAbort SVGError SVGLoad SVGResize SVGScroll SVGUnload",
SVGZoomEvent: "SVGZoom",
TouchEvent: "touchcancel touchend touchenter touchleave touchmove touchstart",
TransitionEvent: "transitionend",
WheelEvent: "wheel"
}
var RecentlyLoggedDOMEventTypes = {};
for (var DOMEvent in DOMEvents) {
var DOMEventTypes = DOMEvents[DOMEvent].split(' ');
DOMEventTypes.filter(function(DOMEventType) {
var DOMEventCategory = DOMEvent + ' ' + DOMEventType;
document.addEventListener(DOMEventType, function(e){
if(RecentlyLoggedDOMEventTypes[DOMEventCategory]) { return; }
RecentlyLoggedDOMEventTypes[DOMEventCategory] = true;
setTimeout(function(){ RecentlyLoggedDOMEventTypes[DOMEventCategory] = false }, 5000);
var isActive = e.target == document.activeElement;
if(isActive) {
console.info(DOMEventCategory,
' target=', e.target,
' active=', document.activeElement,
' isActive=', true );
} else {
console.log(DOMEventCategory,
' target=', e.target,
' active=', document.activeElement,
' isActive=', false );
}
}, true);
});
}
You can iterate through all properties of dom element and select ones that match /on(.*)/ pattern (for example onclick or onmousemove):
var events = [];
for (var property in element) {
var match = property.match(/^on(.*)/)
if (match) {
events.push(match[1]);
}
}
console.log(events.join(' '))
I highly doubt there's a way to do this in Firefox. Looking at Firebug's source code (particularly the attachAllListeners method), turns out that iterating through a list of event names is obviously the way to go, but this doesn't solve the bubbling issues.
There doesn't seem to be any 'easy-way' to do that.
My idea:
You know which are all the events, so you can handle all events for every DOM element:
var events =
[
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
...
];
var root = document.body;
var elms = root.childNodes;
for(var i = 0; i < elms.length; i++)
{
for(var j = 0; j < events.length; j++)
{
elms[i][events[j]] = globalHandler;
}
}
function globalHandler()
{
alert("Global handler called");
}
That's the 'intuitive idea' but doesn't seem to be very efficient. However, it should work.
Good luck.
How to listen for all events on a specific target Element 👾
For all native events, we can retrieve a list of supported events by iterating over the target.onevent properties and installing our listener for all of them.
for (const key in target) {
if(/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener);
}
}
The only other way that events are emitted which I know of is via EventTarget.dispatchEvent, which every Node and thefore every Element inherits.
To listen for all these manually triggered events, we can proxy the dispatchEvent method globally and install our listener just-in-time for the event whose name we just saw ✨ ^^
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) {
if (!alreadyListenedEventTypes.has(event.type)) {
target.addEventListener(event.type, listener, ...otherArguments);
alreadyListenedEventTypes.add(event.type);
}
dispatchEvent_original.apply(this, arguments);
};
🔥 function snippet 🔥
function addEventListenerAll(target, listener, ...otherArguments) {
// install listeners for all natively triggered events
for (const key in target) {
if (/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener, ...otherArguments);
}
}
// dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
function dispatchEvent(event) {
target.addEventListener(event.type, listener, ...otherArguments); // multiple identical listeners are automatically discarded
dispatchEvent_original.apply(this, arguments);
}
EventTarget.prototype.dispatchEvent = dispatchEvent;
if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);
}
// usage example
addEventListenerAll(window, (evt) => {
console.log(evt.type);
});
document.body.click();
document.body.dispatchEvent(new Event('omg!', { bubbles: true }));
// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
window,
(evt) => { console.log(evt.type); },
true
);
document.body.dispatchEvent(new Event('omfggg!', { bubbles: false }));
A bit late to the party but I did create something that might be useful for others here.
https://codepen.io/phreaknation/pen/QmJjEa
This is an ES6 Class that captures all events from an element that is known to that element. This demo allows you to change the element time in the page, as well as read out the events with clickable links to their MDN page as well as interact with the element and see how the events are triggered with time stamps.
I hope this helps
Class code
class EventSystem {
constructor(element) {
this._ = {
element: null
}
return this;
}
getAllEventTypes({blacklist = [], whitelist = []} = {}) {
const events = [];
for (let property in this._.element) {
const match = property.match(/^on(.*)/);
if (match) {
if ((whitelist.length > 0 ? whitelist.indexOf(match) !== -1 : true) &&
(blacklist.length > 0 ? blacklist.indexOf(match) === -1 : true)) {
events.push(match[1]);
}
}
}
return events;
}
getElementType() {
return this._.element.tagName.toLowerCase();
}
setElement(element) {
this._.element = element;
return this;
}
applyEvents(events, callback) {
events.forEach((event) => {
this._.element.addEventListener(event, (ev) => {
if (typeof callback === 'function') {
callback(event, ev);
}
})
})
}
}
My solution to this problem. I loop through all datatypes on the global context (window, in this case), check if the type extends EventTarget, and then extracts them via checking for the "on" prefix.
const getEventNames = (root) => {
let events = [ ];
const objectHasSubPrototype = (object, comp) => {
let proto = Object.getPrototypeOf(object);
while(proto !== null && proto !== EventTarget) {
proto = Object.getPrototypeOf(proto);
}
return (proto !== null);
};
const addEventNames = (propNames) => {
propNames.filter(x => x.match(/^on\w+$/)).forEach((propName) => {
propName = propName.substr(2);
if(events.indexOf(propName) === -1) {
events.push(propName);
}
});
};
Object.getOwnPropertyNames(root).forEach((name) => {
let value = root[name];
if(value) {
if(objectHasSubPrototype(value, EventTarget)) {
let propNames = Object.getOwnPropertyNames(Object.getPrototypeOf(value).prototype);
addEventNames(propNames);
propNames = Object.getOwnPropertyNames(window);
addEventNames(propNames);
}
}
});
return events;
};
// Attach all events to the window
getEventNames(window).forEach((eventName) => {
window.addEventListener(eventName, (event) => console.log(eventName, event));
});
For the last version of the MDN website:
(function getAllEventTypes(){
if(location.href !='https://developer.mozilla.org/en-US/docs/Web/Events') return;
var types = {};
$('.standard-table').map(function(){
if($(this).find('caption').length > 0){
var type = $(this).find('caption')[0].innerHTML || 'OtherEvent';
types[type] = types[type] || [];
$(this).find('tbody tr td code a').each(function(el){
if(this.innerText) types[type].push(this.innerText);
});
}
});
for(var t in types) types[t] = types[t].join(' ');
return "var DOMEvents = "+JSON.stringify(types, null, 4).replace(/"(\w+)\":/ig, '$1:');
})();