周末没事写个钢琴耍耍
// 音频可视化
class Visualization {
constructor(el) {
this.canvas = document.querySelector(el)
this.ctx = this.canvas.getContext('2d')
}
// 初始化音频源
visualization(audEl) {
// 创建音频上下文
const audCtx = this.audCtx || new AudioContext()
this.audCtx = audCtx
// 创建音频源节点 (什么是音频源?就是音频可编辑对象)
const source = audCtx.createMediaElementSource(audEl)
// 创建音频分析器
const analyser = audCtx.createAnalyser()
// 分析音频波形,
// 分析器配置 配置分析器窗口大小,根据分析器大小会计算出不出不同的波形
analyser.fftSize = 512
// 用数组保存分析器返回的音频节点数据 传入节点长度
const dataArray = new Uint8Array(analyser.frequencyBinCount)
// 连接输出设备
source.connect(analyser)
analyser.connect(audCtx.destination)
this.analyser = analyser
this.dataArray = dataArray
this.canvasView()
}
// 绘制波形
canvasView() {
requestAnimationFrame(() => this.canvasView())
const { width, height } = this.canvas
// 清空画布
this.ctx.clearRect(0, 0, width, height)
// 让分析器节点开始分析数据并保存到数组中
this.analyser.getByteFrequencyData(this.dataArray)
// 开始绘制
const len = this.dataArray.length / 5
// 条状的宽度
const ctxWidth = width / len / 1.2
this.ctx.fillStyle = '#78c5f7'
for (var i = 0; i < len; i++) {
const item = this.dataArray[i]
// 柱高度
const ctxHeight = (item / 250) * height
// 计算柱的x和y坐标 (对称绘制)
const x1 = i * ctxWidth + width / 2
const x2 = width / 2 - (i + 1) * ctxWidth
const y = height - ctxHeight
this.ctx.fillRect(x1, y, ctxWidth - 1, ctxHeight)
this.ctx.fillRect(x2, y, ctxWidth - 1, ctxHeight)
}
}
}
// 钢琴类
class Piano extends Visualization {
// 键数组
itemArr = []
constructor(tone = 'ZXCVBNMSDFGHJKWERTYUI', el = '#app') {
super('canvas')
this.tone = tone.split('')
this.el = document.querySelector(el)
this.createDom()
}
// 创建键dom
createDom() {
const domfigs = document.createDocumentFragment()
this.tone.forEach((item, index) => {
const div = document.createElement('div')
if (index + 1 !== this.tone.length) {
const lastdiv = document.createElement('div')
lastdiv.className = 'subsidiary'
div.append(lastdiv)
}
div.className = 'item'
div.dataset.ys = item
const span = document.createElement('span')
span.className = 'span'
span.dataset.ys = item
if (index == 0 || index == 7 || index == 14) {
switch (index) {
case 0:
span.innerHTML = 'C1'
break
case 7:
span.innerHTML = 'C2'
break
case 14:
span.innerHTML = 'C3'
break
default:
break
}
} else {
span.innerHTML = item
}
div.append(span)
domfigs.append(div)
})
this.el.append(domfigs)
this.itemArr = document.querySelectorAll('.item') || []
this.itemArr = Array.from(this.itemArr)
this.eventBind()
}
// 琴键按下效果
actveItem(code) {
const find = this.itemArr.find((item) => item.dataset.ys == code)
if (find) {
find.classList.add('actve')
this.pronunciation(find.dataset.ys)
setTimeout(() => {
find.classList.remove('actve')
}, 300)
}
}
// 鼠标点击事件
eventBind() {
this.el.onclick = (e) => {
if (!e.target.dataset.ys) return
const target = e.target
this.actveItem(target.dataset.ys)
}
}
// 琴键声音
pronunciation(code) {
let indexs = this.tone.findIndex((v) => v == code)
if (indexs < 0) return
indexs++
const src = './video/' + indexs + '.mp3'
const voiceContent = new Audio()
voiceContent.src = src
voiceContent.play()
voiceContent.onplay = (e) => this.visualization(voiceContent)
}
}
const p = new Piano()
window.onkeydown = function (e) {
const codeKey = e.code.substr(-1)
p.actveItem(codeKey)
}
css部分
html{
background: #000;
}
main{
position: relative;
width: 860px;
height: 240px;
display: flex;
justify-content: center;
/* background: #434141; */
background: linear-gradient(25deg, rgb(219, 102, 121), rgb(185, 144, 123), rgb(139, 177, 126), rgb(41, 206, 128));
margin: 100px auto;
padding:0 20px 20px 20px;
box-shadow: 0px 0px 25px 2px rgb(255, 255, 255) inset,1px 0px 15px 10px #000;
perspective: 5000px;
border-radius: 5px;
transform-style: preserve-3d;
}
.gaiban{
position: absolute;
left: 0;
right: 0;
height: 45%;
background: linear-gradient(25deg, rgb(219, 102, 121), rgb(185, 144, 123), rgb(139, 177, 126), rgb(41, 206, 128));
box-shadow: 0px 0px 50px 2px #000;
border-radius: 5px;
transform-origin: top;
transform: translateZ(20px) rotateX(20deg);
}
.gaiban canvas{
width: 100%;
height: 115px;
}
.item{
position: relative;
width: calc(100%/21);
height: 100%;
background: #fff;
margin: 0 1px;
border-radius: 5px;
transform-origin:top;
color: #999;
text-align: center;
font-size: 14px;
user-select: none;
cursor: pointer;
transition: all 0.3s;
}
.item .subsidiary{
position: absolute;
width: 50%;
height: 75%;
right: -25%;
z-index: 999;
background: #000;
border-radius: 6px;
transform-origin: top;
box-shadow: 0px 0px 8px 1px #ddd inset;
}
.item .span{
position: absolute;
left: 0;
right: 0;
bottom: 15px;
}
.item.actve{
background: rgba(255, 255, 255, 0.4);
/* transform: rotateX(5deg); */
}
最后就是html部分了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>钢琴</title>
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<main id="app">
<div data-ys="" class="gaiban">
<canvas width="100%" height="100%"></canvas>
</div>
</main>
<script src="./js/index.js"></script>
</body>
</html>