I have several animations that show and hide elements, and if the user clicks too quickly several animations will play at the same time which creates bizarre glitches.
Right now I'm using a global is_freeze_input variable to prevent certain buttons from being pressed while animations are playing, but I have so many functions which each toggle this variable, so clicking rapidly can sometimes bypass this method of preventing input since the asynchronous nature of JavaScript means I never know when one function is freezing the input or another is unfreezing it.
I thought the cleanest solution would be to disable all input while any animations are playing. Is there a way to do that without manually toggling a variable at the beginning and end of the function?
If I use an event listener to check for all key presses and clicks, can I ignore the input entirely if any part of my code is still processing?
Edit: I have a solution that almost works using the transitionend event listener trigger. This works to disable clicks too soon, but I'm unable to get keypresses to resume after the first time the function is called.
What am I doing wrong?
const handleInput = () => {
document.removeEventListener('keydown', checkKeyDown)
document.removeEventListener('keyup', checkKeyUp)
document.removeEventListener('mouseup', handleInput)
document.removeEventListener('keyup', handleInput)
document.addEventListener('transitionend', () => {
console.log('handleClick transition end')
document.addEventListener('keydown', checkKeyDown)
document.addEventListener('keyup', checkKeyUp)
document.addEventListener('mouseup', handleInput)
document.addEventListener('keyup', handleInput)
}, { once: true })
}
document.addEventListener('keydown', checkKeyDown)
document.addEventListener('keyup', checkKeyUp)
document.addEventListener('mouseup', handleInput)
document.addEventListener('keyup', handleInput)
You can do something like this:
To disable the keyboard inputs:
document.onkeydown = () => false
To enable them back again:
document.onkeydown = () => true
To disable clicks:
document.onclick = () => false
To enable them back again:
document.onclick = () => true
Edit:
To implement this in your project, I would suggest you do something like this:
Create a function to disable all at the same time
function disableAll() {
document.onkeydown = () => false;
document.onclick = () => false;
}
Create a function to enable all at the same time
function enableAll() {
document.onkeydown = () => true;
document.onclick = () => true;
}
And then, as bjb568 suggested, you could use a variable:
that starts at zero, is incremented when an animation starts, and is decremented with one ends.
So that when it is cero, no animations are running. You could check after every animation end, if without that animation, the variable is cero, and if it is, you could call enableAll(), and you could call disableAll() at the start of each animation.
Well, I hope I made myself clear, and I wish you luck with your project, Happy Coding!
Related
Let's say I open a popup overlay box with a background that can be clicked to close it.
Then in that box I have a text input.
If the user drags their mouse to select all the text from the input and overshots by releasing the mouse over the background, then the click event fires and the whole popup closes.
Of course I could use other events on that background like mousedown or pointerdown but it leads to a loophole with all sorts of other issues. For example, if the background uses that then all other elements of the page should use the same type of event in order to make the stopPropagation() calls work. This creates many problems, such as the fact that a button mousedown/pointerdown will trigger before an input blur, and so on.
Is there a way to know from the click event of that background that the mousedown was not initiated on that same element? (meaning it was a drag)
Otherwise, is there another simple way to deal with this issue?
CODE EXAMPLE: https://jsfiddle.net/4aLz1ft7/
EDIT:
Of course it should be a reasonably simple solution (or as simple as possible). Adding an event on every input is not realistic because this content could be dynamic, could contain anything else. Ideally the solution would happen with the wrappers themselves (#overlay and #overlay_content).
so this is working by removing the click event from your overlay using a mousedown event on the content, and then adding it back 1 second after mouseup. I add this back using either of two different events, mouseup on overlay and content.
1 second is just a basic time, it could work properly with less.
I'm not sure why .stopPropagation() or .stopImmediatePropagation() is not working though...
updated the fiddle:
https://jsfiddle.net/1763toyx/1/
ele_overlay.addEventListener('click', overlay_close, false);
ele_overlay_content.addEventListener('mousedown', (v) => {
ele_overlay.removeEventListener('click', overlay_close);
}, false);
ele_overlay_content.addEventListener('mouseup', (v) => {
ele_overlay.removeEventListener('click', overlay_close);
setTimeout(() => {
ele_overlay.addEventListener('click', overlay_close, false);
}, 1000);
}, false);
ele_overlay.addEventListener('mouseup', (v) => {
setTimeout(() => {
ele_overlay.addEventListener('click', overlay_close, false);
}, 1000);
}, false);
UPDATE: Cleaner, but still specific implementation
https://jsfiddle.net/7jbdkovy/2/
function assignListener() {
ele_overlay.addEventListener('click', overlay_close, false);
}
assignListener();
function removeListener() {
ele_overlay.removeEventListener('click', overlay_close);
}
ele_overlay_content.addEventListener('mousedown', (v) => {
removeListener();
}, false);
ele_overlay_content.addEventListener('mouseup', (v) => {
removeListener();
setTimeout(() => {
assignListener();
}, 1);
}, false);
ele_overlay.addEventListener('mouseup', (v) => {
setTimeout(() => {
assignListener();
}, 1);
}, false);
All right, if we're allowed to go (somewhat) dirty, I have another approach than the one #ThisGuy mentioned.
https://jsfiddle.net/1e0yjofc/1/
const ele_overlay = document.getElementById('overlay');
const ele_overlay_content = document.getElementById('overlay_content');
function overlay_open(){
ele_overlay.classList.add('open');
}
function overlay_close(){
ele_overlay.classList.remove('open', 'waitingForClick');
}
ele_overlay_content.addEventListener('pointerdown', function(v){
v.stopPropagation();
}, false);
ele_overlay_content.addEventListener('click', function(v){
v.stopPropagation();
ele_overlay.classList.remove('waitingForClick');
}, false);
ele_overlay.addEventListener('pointerdown', function(v){
ele_overlay.classList.add('waitingForClick');
}, false);
ele_overlay.addEventListener('click', function(v){
if( ele_overlay.classList.contains('waitingForClick') )
overlay_close();
}, false);
The idea is to set a class on the wrapper (background) of the overlay that allows the click event to trigger. And we only allow it if it is preceded by a pointerdown event, because as far as I know a click cannot happen without a pointerdown, or can it?
Of course this could also be a variable instead of a class, but then we would have to deal with declaring it somewhere and it would also make it hard to scale with highly dynamic content (like a whole library). Right now the whole code still doesn't need any global variable since the getElementById calls can be made in the functions themselves.
I find that slightly less dirty than messing around with timers, but if someone has a simpler idea I would love to hear it.
I want to get whether the current mouse is pressed, such as the right button.
I know that you can use mousedown to maintain a variable to determine whether it has been pressed.
But when mouseup on a disabled element, there is no way to set state to up, which leads to subsequent state errors.
Is there an api to help me get the current mouse state?
example
<button disabled>disabled</button>
let state = 'up'
document.addEventListener('mousedown', () => {
state = 'down'
console.log('mouseDown')
})
document.addEventListener('mouseup', () => {
state = 'up'
console.log('mouseup')
})
document.addEventListener('mousemove', () => {
console.log('mousemove',state)
})
according to the the HTML Spec
A form control that is disabled must prevent any click events that are queued on the user interaction task source from being dispatched on the element.
So you can hack the behaviour using Capturing pointer events
give your button an id ex: "mybutton"
button=document.getElementById('mybutton')
button.addEventListener('click',()=>{
console.log('click')
})
button.addEventListener('pointerdown', event => {
console.log('down')
});
button.addEventListener('pointerup', event => {
console.log('up')
});
OR remove the disabled attribute and use css pointer-events: none this will prevent the button from responding and also you will still be able to get the mouse events
I have a MDC Menu component, where I would like to setup a click or select event listener for list items. I have tried with various events like 'click', 'select', 'selectionchange', 'selectstart', but none of them worked. In documentation it says that MDCMenu:selected
Used to indicate when an element has been selected. This event also includes the item selected and the list index of that item.
I have tried to listen to that event as well, but nothing has worked:
menu.listen("MDCMenu:selected", e => console.log(e));
You can see the sandbox here.
How should the event listener be setup for MDC menu component?
Update
Since after I was informed in the comments that for other users the code is actually working. I went to test it myself with other browsers, and in safari and firefox it was working fine on my mac machine, but in Chrome I still have a problem. I have the chrome version 83.0.4103.61. I have updated the codesandbox with suggestions from the comments, but I can now see that if press few times select options, that it starts to work very randomly all of a sudden in all of the browsers.
It appears that the inconsistency is due to a race condition. Clicking on the menu causes the focus to leave the input which causes the menu to close. And the menu closing causes focus to move back to the input making it open again.
The issue is that the menu often closes before the menu has a chance to send out the selected event.
You need to either prevent the menu from closing on focusout or set a generous timeout before closing the menu.
input.listen("focusout", () => {
setTimeout(() => {
menu.open = false;
// The timer might need to be fiddled with. Needs to not be too big or too small.
}, 120);
});
https://codesandbox.io/s/new-brook-o7k1j?file=/src/index.js
Here's another option without the timing issues of setTimeout. It uses timeouts of 0 to mimic setInterval in order to re-order the timing of events instead by pushing them to the end of the event queue. This should be safer and less prone to the race condition issue from before.
let menuFocused = false;
input.listen("focusin", () => {
if (!menuFocused) menu.open = true;
});
input.listen("click", () => {
menu.open = true;
});
menu.listen("focusin", () => {
menuFocused = true;
});
menu.listen("focusout", () => {
// This interval is to help make sure that input.focusIn doesn't re-open the menu
setTimeout(() => {
menuFocused = false;
}, 0);
});
input.listen("focusout", () => {
setTimeout(() => {
if (!menuFocused) menu.open = false;
}, 0);
});
https://codesandbox.io/s/proud-hooks-c9qi5?file=/src/index.js
Here's my situation:
I have a custom menu on right mouse click for my project. Here, I have document.addEventListener on click, that makes this menu invisible, like this:
var i = document.getElementById("menu").style;
document.addEventListener('click', function(e) {
i.opacity = "0";
setTimeout(function() {
i.visibility = "hidden";
}, 100);
}, false);
And it's great, works well, but I'm implementing a dropdown submenu that should be opened when you click on a certain element, like so:
$('#change_color').click(function(){
if($('#back_color').hasClass('back_color')){
$('#back_color').removeClass('back_color')
}else{
$('#back_color').addClass('back_color')
}
})
The thing is that when I click on that #change_color then addEventListener is firing, which is obvious.
The question is – how can I prevent that listener function to execute when I click on #change_color?
You can prevent further propagation of the current event in the capturing and bubbling phases using event.stopPropagation() and for simplicity in your code use jQuery.toggleClass().
Code example:
$('#change_color').click(function (e) {
$('#back_color').toggleClass('back_color');
e.preventDefault();
e.stopPropagation();
});
I am trying to make it so that when a user clicks down, this happens.
In order,
Does something. (Not being specific, this isn't the important part.)
Mouse up is triggered.
Using: angular, html, css.
Not using: jQuery
Any suggestions?
Thanks!
You attach two event listeners, one while the user has the mouse pressed down mousedown. Once the user lets go the mouseup event is triggered. All mouse event listeners are passed an event object you can use to get information about the event ie: mouse x, and y positions.one of the methods available is event.preventDefault() this will stop the browser doing what it usually wants to do. Example: cmd/ctrl + s will cause the browser to save the html page. preventDefault will stop this.
document.addEventListener('mousedown' function (event) {
// Do something
})
document.addEventListener('mouseup', function (event) {
event.preventDefault()
})
To address OP comment:
var noMouseUp = true
document.addEventListener('mousedown', function () {
if (noMouseUp) {
// do something
noMouseUp = false
}
})
document.addEventListener('mouseup', function (event) {
if (!noMouseUp) {
noMouseUp = true
}
})
Use events.preventDefault(); in the mouseup callback.
Use, right after, event.stopPropagation(); to avoid the event passing to other layered elements.
bypassed_element.onclick = function(event){
event.preventDefault();
event.stopPropagation();
};