javascript - 即使使用 !event.repeat,键盘事件也会触发两次

标签 javascript keyboard-events

我一直在制作一个使用键盘作为输入的在线钢琴。有时,当您按下某个键时,它会注册两个事件而不是一个。

问题是,我尝试使用 e.repeat (其中 e 是键盘事件)来仅注册第一个 keydown 事件。

您可以尝试通过此临时链接使用它:https://virtual-piano-demo.netlify.app/

总体而言,我对 JavaScript 和 Web 开发还比较陌生,因此我们非常感谢您对我的代码提出任何建议。

if (!e.repeat && isValidInput(e.key)) {
        let pitch = keysMap.get(e.key);
        playNote(pitch);
        document.getElementById(pitch).classList.add("key-bkg-color"); // change color of key
    }

这是相关代码。

let notes = document.getElementsByClassName("note");
let soften = document.getElementById("soften");
let sustain = document.getElementById("sustain");
let piano = document.getElementById("piano");
let context = new (window.AudioContext || window.webkitAudioContext)();
let keysMap = new Map([
    ['q', "C3"],
    ['2', "Db3"],
    ['w', "D3"],
    ['3', "Eb3"],
    ['e', "E3"],
    ['r', "F3"],
    ['5', "Gb3"],
    ['t', "G3"],
    ['6', "Ab3"],
    ['y', "A3"],
    ['7', "Bb3"],
    ['u', "B3"],
    ['i', "C4"],
    ['9', "Db4"],
    ['o', "D4"],
    ['0', "Eb4"],
    ['p', "E4"],
    ['[', "F4"],
    ['=', "Gb4"],
    [']', "G4"],
    ['a', "Ab4"],
    ['z', "A4"],
    ['s', "Bb4"],
    ['x', "B4"],
    ['c', "C5"],
    ['f', "Db5"],
    ['v', "D5"],
    ['g', "Eb5"],
    ['b', "E5"],
    ['n', "F5"],
    ['j', "Gb5"],
    ['m', "G5"],
    ['k', "Ab5"],
    [',', "A5"],
    ['l', "Bb5"],
    ['.', "B5"],
    ['/', "C6"],
]);

// load all audio files when page loads so that when the user presses keys the sounds start on time
// store buffers in array to access piano sounds when user presses a key
let buffers = [];
window.addEventListener("load", function() {
    for (let i = 0; i < notes.length; ++i) {
        let request = new XMLHttpRequest();
        request.open("GET", "./audio/" + notes[i].id + ".mp3");
        request.responseType = "arraybuffer";
        request.onload = function() {
            let undecodedAudio = request.response;
            context.decodeAudioData(undecodedAudio, (data) => buffers[notes[i].id] = data)
        }
    request.send();
    }
})
function isValidInput(input) {
    return keysMap.has(input) === true;
}

function playNote(pitch) {
    let playSound = context.createBufferSource();
    let gainNode = context.createGain();
    playSound.buffer = buffers[pitch];  

    if (soften.checked === true) {
        gainNode.gain.setValueAtTime(0.6, context.currentTime);
        gainNode.gain.exponentialRampToValueAtTime(1.05, context.currentTime + 0.35);
    }
    else {
        gainNode.gain.setValueAtTime(1.5, context.currentTime);
    }

    playSound.connect(gainNode);
    gainNode.connect(context.destination);
    playSound.start(context.currentTime);

    if (sustain.checked === false) {
        gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 3);
    }
}

// Keyboard Input
document.addEventListener("keydown", function(e) {
    // the pressed key is used for input in the piano -> play note
    // !e.repeat makes sure that only the first keydown event when holding down on a certain key is taken into account
    if (!e.repeat && isValidInput(e.key)) {
        let pitch = keysMap.get(e.key);
        playNote(pitch);
        document.getElementById(pitch).classList.add("key-bkg-color"); // change color of key
    }
})

document.addEventListener("keyup", function(e) {
    let pitch = keysMap.get(e.key);
    document.getElementById(pitch).classList.remove("key-bkg-color"); // change color of key
})

最佳答案

我很确定这是一个严重的JavaScript错误,我能够重现这个问题,例如你按住Q,然后按住W,然后你在按住W的同时只释放Q,它将触发 W 事件。

这就是我所做的,我创建了一个按下的按键数组,并且只有在松开按键时才能再次播放相应的音高,并且在按下按键时我从按下的按键数组中删除该按键:

我还添加了小写逻辑,因为如果激活大写锁定,钢琴将无法工作

let notes = document.getElementsByClassName("note");
let soften = document.getElementById("soften");
let sustain = document.getElementById("sustain");
let keyAssist = document.getElementById("key-assist");
let piano = document.getElementById("piano");
let context = new (window.AudioContext || window.webkitAudioContext)();
let keysMap = new Map([
  ["q", "C3"],
  ["2", "Db3"],
  ["w", "D3"],
  ["3", "Eb3"],
  ["e", "E3"],
  ["r", "F3"],
  ["5", "Gb3"],
  ["t", "G3"],
  ["6", "Ab3"],
  ["y", "A3"],
  ["7", "Bb3"],
  ["u", "B3"],
  ["i", "C4"],
  ["9", "Db4"],
  ["o", "D4"],
  ["0", "Eb4"],
  ["p", "E4"],
  ["[", "F4"],
  ["=", "Gb4"],
  ["]", "G4"],
  ["a", "Ab4"],
  ["z", "A4"],
  ["s", "Bb4"],
  ["x", "B4"],
  ["c", "C5"],
  ["f", "Db5"],
  ["v", "D5"],
  ["g", "Eb5"],
  ["b", "E5"],
  ["n", "F5"],
  ["j", "Gb5"],
  ["m", "G5"],
  ["k", "Ab5"],
  [",", "A5"],
  ["l", "Bb5"],
  [".", "B5"],
  ["/", "C6"],
]);

// load all audio files when page loads so that when the user presses keys the sounds start on time
// store buffers in array to access piano sounds when user presses a key
let buffers = [];
window.addEventListener("load", function () {
  for (let i = 0; i < notes.length; ++i) {
    let request = new XMLHttpRequest();
    request.open("GET", "./audio/" + notes[i].id + ".mp3");
    request.responseType = "arraybuffer";
    request.onload = function () {
      let undecodedAudio = request.response;
      context.decodeAudioData(undecodedAudio, (data) => (buffers[notes[i].id] = data));
    };
    request.send();
  }
});

function isValidInput(input) {
  return keysMap.has(input) === true;
}

function playNote(pitch) {
  let playSound = context.createBufferSource();
  let gainNode = context.createGain();
  playSound.buffer = buffers[pitch];

  if (soften.checked === true) {
    gainNode.gain.setValueAtTime(0.4, context.currentTime);
    gainNode.gain.exponentialRampToValueAtTime(0.85, context.currentTime + 0.35);
  } else {
    gainNode.gain.setValueAtTime(1.5, context.currentTime);
  }

  playSound.connect(gainNode);
  gainNode.connect(context.destination);
  playSound.start(context.currentTime);

  if (sustain.checked === false) {
    gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 3);
  }
}

var currentlyPressedKeys = []; //<========== ADDED HERE ###########

// Keyboard Input
document.addEventListener("keydown", function (e) {
  let key = e.key.toLowerCase();

  if (!e.repeat && isValidInput(key) && !currentlyPressedKeys.includes(key)) { //<========== ADDED HERE ###########
    let pitch = keysMap.get(key);
    playNote(pitch);
    document.getElementById(pitch).classList.add("key-bkg-color");
    currentlyPressedKeys.push(key); //<========== ADDED HERE ###########
  }
});

document.addEventListener("keyup", function (e) {
  let key = e.key.toLowerCase();

  let pitch = keysMap.get(key);

  if (document.getElementById(pitch)) {
    document.getElementById(pitch).classList.remove("key-bkg-color");
  }
  currentlyPressedKeys = currentlyPressedKeys.filter((pressedKey) => pressedKey != key); //<========== ADDED HERE ###########
});

// Mouse Input
let isDown;
document.activeElement.addEventListener("mousedown", function (e) {
  if (e.target.tagName === "BUTTON") {
    playNote(e.target.id);
    e.target.classList.add("key-bkg-color");
    isDown = true;
  }
});

document.activeElement.addEventListener("mouseup", function (e) {
  e.target.classList.remove("key-bkg-color");
  isDown = false;
});

piano.addEventListener("mouseover", function (e) {
  if (e.target.tagName == "BUTTON" && isDown === true) {
    playNote(e.target.id);
    e.target.classList.add("key-bkg-color");
  }
});

piano.addEventListener("mouseout", function (e) {
  e.target.classList.remove("key-bkg-color");
});

// Label Notes
keyAssist.addEventListener("click", function () {
  if (this.checked) {
    for (let i = 0; i < notes.length; ++i) {
      notes[i].style.fontSize = "1.8rem";
    }
  } else {
    for (let i = 0; i < notes.length; ++i) {
      notes[i].style.fontSize = "0";
    }
  }
});

// Menu Open Animation
let body = document.querySelector("body");
let menuBtn = document.getElementById("menu");
let isOpen = false;
menuBtn.addEventListener("click", function () {
  if (!isOpen) {
    body.style.left = "-30rem";
    isOpen = true;
  } else {
    body.style.left = "0";
    isOpen = false;
  }
});

关于javascript - 即使使用 !event.repeat,键盘事件也会触发两次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72960064/

相关文章:

cordova - 在键盘上隐藏页脚打开 Ionic3

jquery PJAX/AJAX PreventDefault() 不阻止链接被单击

javascript - 如何使用 CoffeeScript 模拟现有代码?

javascript - 当选中一个复选框时,使用 javascript 取消选中其他复选框

c# 将键盘命令发送到另一个窗口/进程

python - Pyqt5中KeyEvent的正确处理,捕获KeyPressEvent的问题

android - 如何在WebView上设置键盘焦点?

javascript - 滚动到可滚动容器 div 内的元素不起作用

javascript - 为什么 Unicode 组合字符顺序在 IDEA 和 Chrome 之间不同?

javascript - 服务器发送的事件,我可以创建多少个事件源