网页版钢琴

周末没事写个钢琴耍耍
  • 先看看效果 移步到 这里 前面部分内容可以不看,直接快捷到后面效果部分

  • git地址 点这里

  • 直接上代码
    js部分 主要两个类型,一个钢琴类和一个音频可视化的类

// 音频可视化
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>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值