I recently have been learning more JavaScript as I am going to be using it a lot for an uping internship. The pany remended many different resources for me to look at, but a friend of mine also suggested this website:
/
I decided to give it a try and started yesterday on the first project which was a JavaScript drumkit.
So the overall project wasn't hard, but I noticed a little bug that was happening. So the project attaches a 'keydown' event listener to several different HTML elements that trigger when either ASDFGHJKJL are pressed. In addition to playing a sound, the listener also attaches a class to the element that adds a little effect to the key being pressed.
To remove the class, we attached a transitionend listener to the elements. So when the CSS property finishes the transition after the key is pressed, the class is then removed and the graphic goes back to normal in the browser.
Now if you press the keys slowly, it works perfectly fine. But I noticed that if you were to hold a key down for a second or two, it appears as though the class that gets added is never removed and the graphic is permanent. I then checked the console and noticed that the transitionend event never fires once it gets stuck like that.
Here is the code from the finished file.
function removeTransition(e)
{
if (e.propertyName !== 'transform')
return;
e.target.classList.remove('playing');
}
function playSound(e)
{
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const key = document.querySelector(`div[data-key="${e.keyCode}"]`);
if (!audio)
return;
key.classList.add('playing');
audio.currentTime = 0;
audio.play();
}
And this is how we attached the transitionend listener, not sure if this would have anything to do with it
const keys = Array.from(document.querySelectorAll('.key'));
keys.forEach(key => key.addEventListener('transitionend', removeTransition));
So we put in the if loop in the removeTransition function so that it doesn't remove the class for each property that is transitioning, but I noticed that when you remove the if loop, the page works perfectly.
So I am not entirely sure what is going on with the transitionend event, and why it just stops firing entirely once it gets stuck like that. And also, why it works when the if loop is removed. Maybe it has more chances to remove the class when it loops through all of the properties? No clue. Was wondering if anyone here might know what is going on.
EDIT:
I altered the playSound function a little bit to try and get to the bottom of this.
function playSound(e)
{
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
if(!audio)
{
return;
}
if(key.classList.contains("playing"))
{
console.log("True");
key.classList.remove("playing");
}
audio.currentTime = 0;
audio.play();
console.log("adding");
key.classList.add("playing");
}
So I added an if statement that checks if the element already contains the "playing" class once it gets stuck. I also added a classList.remove call to try and get rid of it in that same branch. What I found was once it gets stuck, and I press the key again, it branches off into that if statement and prints the console message, but STILL will not remove the "playing" class.
I recently have been learning more JavaScript as I am going to be using it a lot for an uping internship. The pany remended many different resources for me to look at, but a friend of mine also suggested this website:
https://javascript30./
I decided to give it a try and started yesterday on the first project which was a JavaScript drumkit.
So the overall project wasn't hard, but I noticed a little bug that was happening. So the project attaches a 'keydown' event listener to several different HTML elements that trigger when either ASDFGHJKJL are pressed. In addition to playing a sound, the listener also attaches a class to the element that adds a little effect to the key being pressed.
To remove the class, we attached a transitionend listener to the elements. So when the CSS property finishes the transition after the key is pressed, the class is then removed and the graphic goes back to normal in the browser.
Now if you press the keys slowly, it works perfectly fine. But I noticed that if you were to hold a key down for a second or two, it appears as though the class that gets added is never removed and the graphic is permanent. I then checked the console and noticed that the transitionend event never fires once it gets stuck like that.
Here is the code from the finished file.
function removeTransition(e)
{
if (e.propertyName !== 'transform')
return;
e.target.classList.remove('playing');
}
function playSound(e)
{
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const key = document.querySelector(`div[data-key="${e.keyCode}"]`);
if (!audio)
return;
key.classList.add('playing');
audio.currentTime = 0;
audio.play();
}
And this is how we attached the transitionend listener, not sure if this would have anything to do with it
const keys = Array.from(document.querySelectorAll('.key'));
keys.forEach(key => key.addEventListener('transitionend', removeTransition));
So we put in the if loop in the removeTransition function so that it doesn't remove the class for each property that is transitioning, but I noticed that when you remove the if loop, the page works perfectly.
So I am not entirely sure what is going on with the transitionend event, and why it just stops firing entirely once it gets stuck like that. And also, why it works when the if loop is removed. Maybe it has more chances to remove the class when it loops through all of the properties? No clue. Was wondering if anyone here might know what is going on.
EDIT:
I altered the playSound function a little bit to try and get to the bottom of this.
function playSound(e)
{
const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`);
const key = document.querySelector(`.key[data-key="${e.keyCode}"]`);
if(!audio)
{
return;
}
if(key.classList.contains("playing"))
{
console.log("True");
key.classList.remove("playing");
}
audio.currentTime = 0;
audio.play();
console.log("adding");
key.classList.add("playing");
}
So I added an if statement that checks if the element already contains the "playing" class once it gets stuck. I also added a classList.remove call to try and get rid of it in that same branch. What I found was once it gets stuck, and I press the key again, it branches off into that if statement and prints the console message, but STILL will not remove the "playing" class.
Share Improve this question edited May 14, 2017 at 21:49 William asked May 14, 2017 at 16:14 WilliamWilliam 4493 gold badges5 silver badges17 bronze badges 2- Can you replicate your issue in an MCVE? I have taken some of your code and put it into a fiddle, but it seems to be working fine. – Terry Commented May 14, 2017 at 22:25
- codepen.io/anon/pen/PmazjN What browser are you using? I am using Chrome. Not sure if that could be it or not, but figured I'd ask. I copied over the HTML, which includes the javascript, and the CSS. I mented out all the sound stuff, since you guys don't have the files for that. I was still able to replicate it, though. – William Commented May 14, 2017 at 23:59
4 Answers
Reset to default 3Use .toggle()
instead of .add()
:
key.classlist.toggle('playing');
instead of
key.classlist.add('playing');
There are 2 causes for this unable to remove CSS class problem:
- The flag to mark whether animation is played is class
playing
, which is also the key to switch on/off the animation. This setting makes the problem very plicated. - The flag/mark change is adding/removing class
playing
. It is a DOM operation which costs more time than necessary.
To solve this problem, it is better to store flag/mark information in memory (as a variable), and stop keyboard event listener if transition is not pleted.
Store flag/mark information in a variable:
var keysUnderTransition = {
"65": 0,
"83": 0,
"68": 0,
"70": 0,
"71": 0,
"72": 0,
"74": 0,
"75": 0,
"76": 0
};
As every element will have 2 transitions in one cycle. 0
means element is not in transition (no transition left), 2
means it's in the first transition (2 transitions left to plete), and 1
means it's in the last transition (1 transition left to plete).
In function playSound
, check and mark the flag:
if (keysUnderTransition[e.keyCode]) {
return;
}
keysUnderTransition[e.keyCode] = 2;
In function removeTransition
, revert flag/mark:
var dataKey = this.getAttribute('data-key');
var underTransitionVal = keysUnderTransition[dataKey];
if (underTransitionVal === 2) {
setTimeout(function() {
keysUnderTransition[dataKey] = 1;
}, 50);
} else if (underTransitionVal === 1) {
keysUnderTransition[dataKey] = 0;
}
I use 50
in the above code to change flag before the end of second transition.
Here is a working jsfiddle.
TL;DR; Add the event handler for transitioncancel
as well:
keys.forEach(key => {
key.addEventListener('transitionend', removeTransition)
key.addEventListener('transitioncancel', removeTransition)
});
In the spec, it mentions that:
the
transitionend
event does not fire in the case where a transition is removed before pletion, such as if the transition-property is removed
Or the simple explanation is in your case the transition was canceled instead of ended, so you would need to add the event handler for transitioncancel
event as well, to make that case works.
keys.forEach(key => {
key.addEventListener('transitionend', removeTransition)
key.addEventListener('transitioncancel', removeTransition)
});
The transitionend event can be interrupted and not fired at all. This can happen if the user scrolls or does any other event with higher priority. You need to include a fallback and cancel it if the transitionend event fires.
I had similar problem with touch scrolling in a mobile app, where I was animating a slider.
So ...
function transitionEndHandler(...){
//remove the class
clearTimeout(fallback);
}
var fallback = setTimeout(function(){
//remove the class
}, timeOfTransition);
If the class doesn't get removed from the transitionend, the setTimeout function will take care of it.