我一直在制作一个在线钢琴,使用键盘作为输入.有时,当你按下一个键时,它会记录两个事件而不是一个.

问题是,我试着用e.repeat(其中e是键盘事件)只注册第一个按键事件.

你可以try 在这个临时链接上使用它: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,然后你只释放Q而仍按住W,它将触发一个W事件.

以下是我所做的,我创建了一个按键数组,只有在释放键的情况下才能再次播放相应的音高,在向上键时,我从按键数组中删除键:

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

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相关问答推荐

详细更改参考价值:"

如何循环访问对象数组并以关键值形式获得结果?

JavaScript文本区域阻止KeyDown/KeyUp事件本身上的Alt GR +键组合

窗口.getComputedStyle()在MutationObserver中不起作用

如何从JSON数组添加Google Maps标记—或者如何为数组添加参数到标记构造器

MongoDB中的引用

Rehype将hashtag呈现为URL

Chart.js 4.4.2,当悬停在一个数据点上时,如何在工具提示中拥有多个数据点/标签?

在带有背景图像和圆形的div中添加长方体阴影时的重影线

在react JS中映射数组对象的嵌套数据

实现JS代码更改CSS元素

Prisma具有至少一个值的多对多关系

您能在卸载程序(QtInsteller框架)上添加WizardPage吗?

如何在使用rhandsontable生成表时扩展数字输入验证?

覆盖加载器页面避免对页面上的元素进行操作

将数组扩展到对象中

一个实体一刀VS每个实体多刀S

<;img>;标记无法呈现图像

更新Redux存储中的对象数组

匹配一个或多个可选重复的特定模式