JSFiddle - React, Tailwind, and code Playground
by gustav75
HTML
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Virtuelles Klavier</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="piano">
<div class="key white" data-note="C" data-key="A"></div>
<div class="key black" data-note="C#" data-key="W" style="left: 50px;"></div>
<div class="key white" data-note="D" data-key="S"></div>
<div class="key black" data-note="D#" data-key="E" style="left: 130px;"></div>
<div class="key white" data-note="E" data-key="D"></div>
<div class="key white" data-note="F" data-key="F"></div>
<div class="key black" data-note="F#" data-key="T" style="left: 290px;"></div>
<div class="key white" data-note="G" data-key="G"></div>
<div class="key black" data-note="G#" data-key="Y" style="left: 370px;"></div>
<div class="key white" data-note="A" data-key="H"></div>
<div class="key black" data-note="A#" data-key="U" style="left: 450px;"></div>
<div class="key white" data-note="B" data-key="J"></div>
<div class="key white" data-note="C2" data-key="K"></div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #333;
}
#piano {
position: relative;
width: 760px;
}
.key {
width: 80px;
height: 300px;
border: 1px solid #000;
box-sizing: border-box;
position: relative;
cursor: pointer;
transition: background-color 0.1s;
}
.white {
background-color: white;
z-index: 1;
float: left;
}
.black {
background-color: black;
width: 60px;
height: 180px;
position: absolute;
top: 0;
z-index: 2;
}
.key.playing.white {
background-color: lightgray;
}
.key.playing.black {
background-color: #555;
}
JavaScript
const notes = {
'C': 'https://lagustav.ch/wp-content/uploads/2024/08/C-6.mp3',
'C#': 'https://lagustav.ch/wp-content/uploads/2024/08/C-7.mp3',
'D': 'https://lagustav.ch/wp-content/uploads/2024/08/D-6.mp3',
'D#': 'https://lagustav.ch/wp-content/uploads/2024/08/D-7.mp3',
'E': 'https://lagustav.ch/wp-content/uploads/2024/08/E-3.mp3',
'F': 'https://lagustav.ch/wp-content/uploads/2024/08/F-6.mp3',
'F#': 'https://lagustav.ch/wp-content/uploads/2024/08/F-7.mp3',
'G': 'https://lagustav.ch/wp-content/uploads/2024/08/G-6.mp3',
'G#': 'https://lagustav.ch/wp-content/uploads/2024/08/G-7.mp3',
'A': 'https://lagustav.ch/wp-content/uploads/2024/08/A-6.mp3',
'A#': 'https://lagustav.ch/wp-content/uploads/2024/08/A-7.mp3',
'B': 'https://lagustav.ch/wp-content/uploads/2024/08/H-3.mp3',
'C2': 'https://lagustav.ch/wp-content/uploads/2024/08/C2-3.mp3'
};
const keyMap = {
'A': 'C',
'W': 'C#',
'S': 'D',
'E': 'D#',
'D': 'E',
'F': 'F',
'T': 'F#',
'G': 'G',
'Y': 'G#',
'H': 'A',
'U': 'A#',
'J': 'B',
'K': 'C2'
};
document.querySelectorAll('.key').forEach(key => {
key.addEventListener('mousedown', () => playNote(key));
key.addEventListener('mouseup', () => removePlayingClass(key));
key.addEventListener('mouseleave', () => removePlayingClass(key));
});
document.addEventListener('keydown', (event) => {
const note = keyMap[event.key.toUpperCase()];
if (note) {
const key = document.querySelector(`.key[data-note="${note}"]`);
if (key) {
playNote(key);
}
}
});
document.addEventListener('keyup', (event) => {
const note = keyMap[event.key.toUpperCase()];
if (note) {
const key = document.querySelector(`.key[data-note="${note}"]`);
if (key) {
removePlayingClass(key);
}
}
});
function playNote(key) {
const note = key.dataset.note;
const audio = new Audio(notes[note]);
...