转换结束事件未正确删除 CSS 类的错误



我最近一直在学习更多的JavaScript,因为我将在即将到来的实习中大量使用它。该公司推荐了许多不同的资源供我查看,但我的一个朋友也推荐了这个网站:

https://javascript30.com/

我决定尝试一下,昨天开始了第一个项目,这是一个JavaScript鼓包。

所以整个项目并不难,但我注意到正在发生的一个小错误。因此,该项目将"keydown"事件侦听器附加到几个不同的 HTML 元素,这些元素在按下 ASDFGHJKJL 时触发。除了播放声音外,侦听器还会将一个类附加到元素上,该类为按下的键添加一点效果。

为了删除该类,我们将一个过渡结束侦听器附加到元素。因此,当 CSS 属性在按下键后完成转换时,该类将被删除,图形在浏览器中恢复正常。

现在,如果您缓慢按下按键,它就可以正常工作。但我注意到,如果你按住一个键一两秒钟,看起来好像添加的类永远不会被删除,图形是永久性的。然后我检查了控制台,注意到 transitionend 事件一旦像这样卡住就永远不会触发。

这是完成文件中的代码。

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();
}

这就是我们附加过渡端侦听器的方式,不确定这是否与它

有关
const keys = Array.from(document.querySelectorAll('.key'));
keys.forEach(key => key.addEventListener('transitionend', removeTransition));

所以我们在 removeTransition 函数中放入了 if 循环,这样它就不会删除正在转换的每个属性的类,但我注意到当你删除 if 循环时,页面可以完美运行。

所以我不完全确定过渡结束事件是怎么回事,以及为什么一旦它像那样卡住就会完全停止发射。还有,为什么当 if 循环被删除时它会起作用。也许当它循环遍历所有属性时,它有更多的机会删除类?没有线索。想知道这里是否有人知道发生了什么。

编辑:

我稍微更改了playSound功能,以尝试深入了解这一点。

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");
}

所以我添加了一个 if 语句,该语句在元素卡住后检查元素是否已经包含"playing"类。我还添加了一个classList.remove 调用,以尝试在同一分支中摆脱它。我发现一旦它卡住,我再次按下键,它就会分支到 if 语句并打印控制台消息,但仍然不会删除"播放"类。

使用.toggle()而不是.add()

key.classlist.toggle('playing'); 

而不是

key.classlist.add('playing');

导致无法删除CSS类问题的原因有2个:

  1. 标记是否播放动画的标志是类playing,这也是打开/关闭动画的关键。此设置使问题非常复杂。
  2. 标志/标记更改正在添加/删除类playing。这是一个 DOM 操作,花费的时间比必要的要多。

要解决此问题,最好将标志/标记信息存储在内存中(作为变量),并在转换未完成时停止键盘事件侦听器。

将标志/标记信息存储在变量中:

var keysUnderTransition = {
"65": 0,
"83": 0,
"68": 0,
"70": 0,
"71": 0,
"72": 0,
"74": 0,
"75": 0,
"76": 0
};

因为每个元素在一个周期内都会有 2 个过渡。0表示元素不在过渡中(没有左过渡),2表示它处于第一个过渡中(剩下 2 个过渡完成),1表示它处于最后一个过渡(左 1 个过渡完成)。

在函数playSound中,选中并标记标志:

if (keysUnderTransition[e.keyCode]) {
return;
}
keysUnderTransition[e.keyCode] = 2;

在函数removeTransition中,还原标志/标记:

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;
}

我在上面的代码中使用50在第二次转换结束之前更改标志。

这是一个工作 jsfiddle。

TL;博士;同时为transitioncancel添加事件处理程序:

keys.forEach(key => {
key.addEventListener('transitionend', removeTransition)
key.addEventListener('transitioncancel', removeTransition)
});

在规范中,它提到:

完成之前删除转换的情况下,例如,如果转换属性被删除,则不会触发transitionend事件

或者简单的解释是,在您的情况下,转换被取消而不是结束,因此您还需要为transitioncancel事件添加事件处理程序,以使该情况正常工作。

keys.forEach(key => {
key.addEventListener('transitionend', removeTransition)
key.addEventListener('transitioncancel', removeTransition)
});

转换结束事件可以中断,根本不触发。如果用户滚动或执行任何其他具有更高优先级的事件,则可能会发生这种情况。您需要包含回退,并在转换结束事件触发时取消它。

我在移动应用程序中遇到类似的触摸滚动问题,我正在为滑块制作动画。

所以。。。

function transitionEndHandler(...){
//remove the class
clearTimeout(fallback);
}
var fallback = setTimeout(function(){
//remove the class
}, timeOfTransition);

如果类没有从转换端中删除,setTimeout 函数将处理它。

最新更新