美文网首页
简易网页钢琴(一)

简易网页钢琴(一)

作者: kevin5979 | 来源:发表于2020-09-07 23:27 被阅读0次
  • 本人小白,练手代码,如有不足,请见谅,也欢迎评论交流

先看效果图

效果图.png

网站预览或源码下载
方式一:http://kevin5979.3vfree.net/share/javaScript/piano/index.html
方式二: http://kevin5979.3vfree.net

  • 账号: admin
  • 密码:123456
Ok,接下来详细讲解我书写代码的过程
  • 我把我认为重要的知识点都写到每个步骤的前面
先构建思路

第一步:建立工程目录,编写html文件和css文件基本布局,完成基本的样式
第二步:获取所有需要的DOM元素对象,设计数据格式,并思考如何存储曲目的数据
第三步:实现键盘按钮事件,onkeydown,拿到每个琴键的key值
第四步:实现鼠标的点击事件,onclick,也是拿到每个DOM元素的对应key
第五步:实现钢琴点击/按键的动态效果
第六步:实现功能按钮的点击事件 onclick
第七步:处理用户交互信息
第八步:列表渲染
第九步:接入声音类,将key转化成对应的琴音
第十步:保存的曲目播放
第十一步:优化代码,解决bug

第一步 编写html文件和css文件基本布局,完成基本的样式

这里就不用细讲了,直接上代码

  • 目录组织
  • flex布局
  • CSS3 animation动画
  • iconfont使用
目录结构

HTML

<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>piano</title>
    <link rel="stylesheet" href="./css/index.css">
  </head>

  <body>
    <div class="piano-box">
      <ul class="key-list">
        <li class="item" data-key="q"><span class="bottom">1</span></li>
        <li class="item" data-key="w"><span class="bottom">2</span></li>
        <li class="item" data-key="e"><span class="bottom">3</span></li>
        <li class="item" data-key="r"><span class="bottom">4</span></li>
        <li class="item" data-key="t"><span class="bottom">5</span></li>
        <li class="item" data-key="y"><span class="bottom">6</span></li>
        <li class="item" data-key="u"><span class="bottom">7</span></li>
        <li class="item" data-key="a"><span>1</span></li>
        <li class="item" data-key="s"><span>2</span></li>
        <li class="item" data-key="d"><span>3</span></li>
        <li class="item" data-key="f"><span>4</span></li>
        <li class="item" data-key="g"><span>5</span></li>
        <li class="item" data-key="h"><span>6</span></li>
        <li class="item" data-key="j"><span>7</span></li>
        <li class="item" data-key="z"><span class="top">1</span></li>
        <li class="item" data-key="x"><span class="top">2</span></li>
        <li class="item" data-key="c"><span class="top">3</span></li>
        <li class="item" data-key="v"><span class="top">4</span></li>
        <li class="item" data-key="b"><span class="top">5</span></li>
        <li class="item" data-key="n"><span class="top">6</span></li>
        <li class="item" data-key="m"><span class="top">7</span></li>
      </ul>
      <ul class="btn-list">
        <li><button class="record">录制</button></li>
        <li><button class="end">结束录制</button></li>
      </ul>
      <h4>曲目单</h4>
      <ul class="music-list">
      </ul>
    </div>
    <script src="./js/index.js" type="module"></script>
  </body>

</html>

CSS

@import "//at.alicdn.com/t/font_1724264_3u1pq0ga9u6.css";

* {
  margin: 0;
  padding: 0;
}

body,
html {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: rgba(82, 82, 82, 0.2);
}

.key-list {
  list-style: none;
  margin: 50px auto 30px auto;
  width: 92%;
  display: flex;
  justify-self: start;
  cursor: pointer;
}

.item {
  width: 4.3%;
  height: 250px;
  background: #fff;
  text-align: center;
  margin-right: 3px;
  user-select: none;
  position: relative;
}

.item span {
  position: absolute;
  left: 50%;
  bottom: 15px;
  transform: translateX(-50%);
  display: block;
}

.item .bottom::after {
  content: ".";
  font-size: 20px;
  font-weight: bold;
  display: block;
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
}

.item .top::before {
  content: ".";
  font-size: 20px;
  font-weight: bold;
  display: block;
  position: absolute;
  top: -20px;
  left: 50%;
  transform: translateX(-50%);
}

.active {
  animation: press 0.5s;
}

@keyframes press {
  0% {
    transform: scale(1);
    box-shadow: none;
  }

  50% {
    transform: scale(1.05);
    box-shadow: 2px 2px 2px 2px rgba(0, 0, 0, 0.2);
  }

  100% {
    transform: scale(1);
    box-shadow: none;
  }
}

.btn-list {
  width: 90%;
  margin: 0 auto;
  list-style: none;
  display: flex;
  justify-content: start;
}

button {
  margin: 0 20px;
  width: 70px;
  height: 70px;
  background-color: aqua;
  cursor: pointer;
}

h4 {
  margin-left: 6%;
  margin-top: 20px;
}

.music-list {
  width: 30%;
  height: 200px;
  margin-left: 6%;
  margin-top: 10px;
  list-style: none;
  background-color: #fff;
  overflow: scroll;
}

.music-list li {
  padding: 5px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 16px;
  user-select: none;
}

.music-list li:nth-child(2n) {
  background: rgb(216, 216, 216);
}

.icon-bofang {
  color: #333;
  font-size: 20px;
  cursor: pointer;
}

.music-list .name {
  color: #333;
  margin-right: 20px;
}

.music-list .time {
  color: #999;
  font-size: 14px;
}

注意点

  1. 由于js代码中需要用到模块化,所以在html书写script标签时需要添加 type="module"
  2. 由于css中导入了阿里图标库iconfont,所以必须联网访问,而且需要在编译器中打开代码,直接打开会报错

第二步:获取所有需要的DOM元素对象,设计数据格式,并思考如何存储曲目的数据
image.png
  • 获取DOM元素
window.onload = function () {
  const keysUl = document.querySelector(".key-list")
  const keysList = keysUl.querySelectorAll("li")
  const btnList = document.querySelector(".btn-list")
  const record = btnList.querySelector(".record")
  const end = btnList.querySelector(".end")
  let musicList = document.querySelector('.music-list')
}
  • 设计数据格式
    由于我们需要做的功能是存储钢琴曲的琴键的按键信息,可以使用数组songs: Array
    这时又想到需要处理每个按键之间的时间距离,可以在使用一个数组stamps: Array
    我们需要一个录制开始时间startStamps: Array,录制结束时间endStamps: Array来作为参照物,才可计算每个按键的时间差,并计算出的每个曲目的总时间songTime: Array,也需要记录
    如果我们需要保存曲目的信息,就必须给保存的数据命名,这时需要一个命名数组names: Array
    这时我们想到需要定义的数据太多了,存储到localStorage不方便,这时定义一个对象resources: Object
let resources = {
    songs: [],
    stamps: [],
    startStamps: [],
    endStamps: [],
    names: [],
    songTime: []
  }

// 定义其他需要的变量,后续随着代码变多,这里的变量可增加
// 定义所有的key值
const keys = "qwertyuasdfghjzxcvbnm"

  • 思考如何存储曲目的数据
    想到每次用户打开网页能得到前面的数据,避免每次关闭网页,数据会消失,这里使用localStorage解决,但是不能直接存储对象类型,这时,我们使用了JSON.stringify(obj)JSON.parse(str)来对我们存储的数据进行对象和字符串之间的转换

第三步:实现键盘按钮事件,onkeydown,拿到每个琴键的key值
  • 注册全局事件
window.onkeydown = function (e) {
  console.log(e.key)  // e.key : a,b,c ...
  showEffect(e.key)   // showEffect为控制动画方法,后面有写
}

第四步:实现鼠标的点击事件,onclick,也是拿到每个DOM元素的对应key
  • 事件代理
  • data-xxx属性的使用
keysUl.onclick = function (e) {
  console.log(e.target.dataset.key)  // a,b,c ...
  key && onViews.showEffect(key)     // showEffect为控制动画方法,后面有写
}
  • 这里使用了事件代理,给父元素添加事件,通过data-xxx属性判断是点击了那个子元素,这样可以避免给所有子元素都添加事件,减少了浏览器的压力,不会产生太大花销,优化性能

第五步:实现钢琴点击/按键的动态效果
  • js控制类名来改变样式
/**
 * 控制钢琴动画效果
 * @param {*} key 按钮对应的key
 */
function showEffect (key) {
  const index = keys.indexOf(key) // 得到下标
  if (index !== -1) {
    // 添加类名,用于动画展示
    keysList[index].classList.add("active")
    // 300ms后移除类
    setTimeout(() => {
      keysList[index].classList.remove('active')
    }, 300)
  }
}
第六步:实现功能按钮的点击事件 onclick
  • 代码触发点击事件
  • 切换状态变量管理
  let isStart = false
  let isEnd = true
  let startstamp = null // 开始录制时间戳
  let endstamp = null   // 结束录制事件戳
  let tempSong = []  // 记录当前录制曲目的按键信息
  let tempStamp = [] // 记录当前录制每个按键之间的时间差
// 开始录制
record.onclick = function () {
  if (isEnd) {
    startstamp = new Date().getTime()
    tempSong = []
    tempStamp = []
    isStart = true
    isEnd = false
  } else {
    const isOver = confirm("当前正在录制,是否结束录制?")
    isOver && end.onclick()
  }
}
// 结束录制
end.onclick = function () {
if (isStart) {
    isStart = false
    isEnd = true
    endstamp = new Date().getTime()
    const isSave = confirm("是否保存曲目?")
    if (isSave) {
      let name = prompt("输入曲目名")
      if (!resources.names.includes(name) && name !== null) {
        // 到这里说明用户完成了保存操作,我们需要将数据整理并保存到本地或全局变量中
        resources.endStamps.push(endstamp)
        resources.startStamps.push(startstamp)
        resources.songs.push(tempSong)
        resources.stamps.push(tempStamp)
        resources.names.push(name)
        resources.songTime.push(endstamp - startstamp)
        localStorage.setItem("resources", JSON.stringify(resources))
       renderList({ name, time: endstamp - startstamp })
      } else {
        alert("保存失败,输入为空或者该名字已存在")
      }
    }
 } else {
    alert("未开始录制")
  }
}
  • 这里需要控制录制状态和结束状态两个状态的转换,这里使用两个变量来控制(isStartisEnd),需要获取当前时间戳,定义变量startstamp,既然开始录制,则需要存储按钮信息tempSong: Array和每个按键的时间差tempStamp:Array
  • 如果当前已经是录制状态,需要提示用户

为避免文章过长,本章先讲解第1~6步,后续会继续写完这个小功能,欢迎点赞,评论
END

相关文章

网友评论

      本文标题:简易网页钢琴(一)

      本文链接:https://www.haomeiwen.com/subject/miulektx.html