年轻人的第一个Vue在线音乐播放器

简介: 年轻人的第一个Vue在线音乐播放器

Preface



前几天听五条人的歌,女和声+乐队的组合就老让我有种既视感,想起了高三的时候天天听的歌。


决定自己写一个全平台都能听的在线播放器,也省的下载了。


东拼西凑整了一个还算舒服的播放器,用到了Vue2.6,有二百多首歌,随机播放,就是电台的感觉,希望你们喜欢👀


在线试听:Github.ioCodepen

源码可以看这里:Github


image.png



HTML



整体框架

🤡


<div class="wrapper" id="app">
  <div class="player">
    <div class="player__top">
      <!-- 封面,暂停/播放,上一首,下一首,❤,🔗 五个按钮-->
    </div>
    <div class="progress" ref="progress">
      <!-- 进度条,歌曲信息,当前播放时长,总时长-->
    </div>
  </div>
</div>
复制代码


按钮都是用SVG画的,这里把 path 都省略了。

网上都有,直接copy源码吧 Github


<svg xmlns="http://www.w3.org/2000/svg" hidden xmlns:xlink="http://www.w3.org/1999/xlink">
  <defs>
    <!-- 空心和实心的❤ -->
    <symbol id="icon-heart-o" viewBox="0 0 32 32">
      <title>icon-heart-o</title>
    </symbol>
    <symbol id="icon-heart" viewBox="0 0 32 32">
      <title>icon-heart</title>
    </symbol>
    <!-- 暂停和播放按钮 -->
    <symbol id="icon-pause" viewBox="0 0 32 32">
      <title>icon-pause</title>
    </symbol>
    <symbol id="icon-play" viewBox="0 0 32 32">
      <title>icon-play</title>
    </symbol>
    <!-- 链接按钮🔗 -->
    <symbol id="icon-link" viewBox="0 0 32 32">
      <title>link</title>
    </symbol>
    <!-- 上一首和下一首 -->
    <symbol id="icon-next" viewBox="0 0 32 32">
      <title>next</title>
    </symbol>
    <symbol id="icon-prev" viewBox="0 0 32 32">
      <title>prev</title>
    </symbol>
  </defs>
</svg>
复制代码


专辑封面


<div class="player-cover">
    <transition-group :name="transitionName">
        <div class="player-cover__item" v-if="$index === currentTrackIndex" 
             :style="{ backgroundImage: `url(${track.cover})` }" 
             v-for="(track, $index) in tracks" :key="$index">
        </div>
    </transition-group>
</div>
复制代码


播放 & 暂停


<div class="player-controls__item -xl js-play" @click="play">
    <svg class="icon">
        <use xlink:href="#icon-pause" v-if="isTimerPlaying"></use>
        <use xlink:href="#icon-play" v-else></use>
    </svg>
</div>
复制代码


上一首 & 下一首


<div class="player-controls__item" @click="prevTrack">
    <svg class="icon">
        <use xlink:href="#icon-prev"></use>
    </svg>
</div>
<div class="player-controls__item" @click="nextTrack">
    <svg class="icon">
        <use xlink:href="#icon-next"></use>
    </svg>
</div>
复制代码


❤ 🔗


<div class="player-controls__item -favorite" 
     :class="{ active : currentTrack.favorited }" 
     @click="favorite">
    <svg class="icon">
        <use xlink:href="#icon-heart-o"></use>
    </svg>
</div>
<a :href="currentTrack.url" target="_blank" class="player-controls__item">
    <svg class="icon">
        <use xlink:href="#icon-link"></use>
    </svg>
</a>
复制代码


歌曲信息 & 进度条


<div class="progress" ref="progress">
    <div class="progress__top">
        <div class="album-info" v-if="currentTrack">
            <div class="album-info__name">{{ currentTrack.artist }}</div>
            <div class="album-info__track">{{ currentTrack.name }}</div>
        </div>
        <div class="progress__duration">{{ duration }}</div>
    </div>
    <div class="progress__bar" @click="clickProgress">
        <div class="progress__current" :style="{ width : barWidth }"></div>
    </div>
    <div class="progress__time">{{ currentTime }}</div>
</div>
<div v-cloak></div>
复制代码


CSS



重点就是 @media 的适配,没啥好说的,代码就放一点儿吧,想看的去Github吧,图中是另一种样式。


image.png



body {
  background: #dfe7ef;
  font-family: "Bitter", serif;
}
* {
  box-sizing: border-box;
}
.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
}
.wrapper {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background-size: cover;
  @media screen and (max-width: 700px), (max-height: 500px) {
    flex-wrap: wrap;
    flex-direction: column;
  }
}
复制代码

Vue



导入Github上找到的音源及Vue2.6:


<script src="https://cdn.jsdelivr.net/gh/nj-lizhi/song@master/audio/list.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
复制代码


整体框架


new Vue({
  el: "#app",
  data() {
    return {
      audio: null,
      circleLeft: null,
      barWidth: null,
      duration: null,
      currentTime: null,
      isTimerPlaying: false,
      tracks: list,    // 导入的音源
      currentTrack: null,
      currentTrackIndex: 0,
      transitionName: null
    };
  },
  methods: {
    play() {
      // 播放 & 暂停
    },
    generateTime() {
      // 时长
    },
    updateBar(x) {
      // 进度条
    },
    clickProgress(e) {
      // 点击进度条调节进度
    },
    prevTrack() {
      // 上一首
    },
    nextTrack() {
      // 下一首
    },
    resetPlayer() {
      // 重置
    },
    favorite() {
      // ❤
    }
  },
  created() {
    let vm = this;
    // 第一首歌固定是“忽然”
    this.currentTrack = this.tracks[3];
    this.audio = new Audio();
    this.audio.src = this.currentTrack.url;
    this.audio.ontimeupdate = function () {
        vm.generateTime();
    };
    this.audio.onloadedmetadata = function () {
        vm.generateTime();
    };
    // 放完自动切换下一首
    this.audio.onended = function () {
        vm.nextTrack();
        this.isTimerPlaying = true;
    };
  }
});
复制代码


播放 & 暂停


play() {
    if (this.audio.paused) {
        this.audio.play();
        this.isTimerPlaying = true;
    } else {
        this.audio.pause();
        this.isTimerPlaying = false;
    }
},
复制代码


时长


generateTime() {
    let width = (100 / this.audio.duration) * this.audio.currentTime;
    this.barWidth = width + "%";
    this.circleLeft = width + "%";
    let durmin = Math.floor(this.audio.duration / 60);
    let dursec = Math.floor(this.audio.duration - durmin * 60);
    let curmin = Math.floor(this.audio.currentTime / 60);
    let cursec = Math.floor(this.audio.currentTime - curmin * 60);
    if (durmin < 10) {
        durmin = "0" + durmin;
    }
    if (dursec < 10) {
        dursec = "0" + dursec;
    }
    if (curmin < 10) {
        curmin = "0" + curmin;
    }
    if (cursec < 10) {
        cursec = "0" + cursec;
    }
    this.duration = durmin + ":" + dursec;
    this.currentTime = curmin + ":" + cursec;
},
复制代码


进度条


updateBar(x) {
    let progress = this.$refs.progress;
    let maxduration = this.audio.duration;
    let position = x - progress.offsetLeft;
    let percentage = (100 * position) / progress.offsetWidth;
    if (percentage > 100) {
        percentage = 100;
    }
    if (percentage < 0) {
        percentage = 0;
    }
    this.barWidth = percentage + "%";
    this.circleLeft = percentage + "%";
    this.audio.currentTime = (maxduration * percentage) / 100;
    this.audio.play();
},
复制代码


点击进度条调节进度


clickProgress(e) {
    this.isTimerPlaying = true;
    this.audio.pause();
    this.updateBar(e.pageX);
},
复制代码


上一首 & 下一首


我是这样设置的:下一首按随机播放来,上一首按顺序来 🏏


prevTrack() {
    this.transitionName = "scale-in";
    if (this.currentTrackIndex > 0) {
        this.currentTrackIndex--;
    } else {
        this.currentTrackIndex = this.tracks.length - 1;
    }
    this.currentTrack = this.tracks[this.currentTrackIndex];
    this.resetPlayer();
},
nextTrack() {
  this.transitionName = "scale-out";
    this.currentTrackIndex = parseInt(this.tracks.length * Math.random());
    this.currentTrack = this.tracks[this.currentTrackIndex];
    this.resetPlayer();
},
复制代码


重置

每次切歌的时候重置:


resetPlayer() {
    this.barWidth = 0;
    this.circleLeft = 0;
    this.audio.currentTime = 0;
    this.audio.src = this.currentTrack.url;
    setTimeout(() => {
        if (this.isTimerPlaying) {
            this.audio.play();
        } else {
            this.audio.pause();
        }
    }, 300);
},
复制代码


点红心的功能弃置了,因为是大佬整理的音源没有相关项,而且这个功能比较鸡肋,没有记忆,❎掉就没了。


favorite() {
    this.tracks[this.currentTrackIndex].favorited = !this.tracks[
        this.currentTrackIndex
    ].favorited;
}
复制代码


Todo



还有一些有待完成的功能等我有时间再说吧😛:

  1. 暗黑模式
  2. 单曲循环,列表循环,列表顺序播放几种播放模式
  3. 列表显示全部歌曲
  4. ……


Summary


虽然我是个菜狗子,但还是被我整出来了。这就是站在前人的肩膀上吗,爱了爱了!

✔ 感谢以下大佬的开源项目:



如果对您有帮助,希望能给个👍评论收藏三连!

欢迎关注互相交流,有问题可以评论留言。

我是Mancuoj,更多有趣文章:Mancuoj 的个人主页

目录
相关文章
|
3月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
359 2
|
2月前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
329 137
|
6月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
839 0
|
6月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
5月前
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
486 1
|
5月前
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
307 0
|
6月前
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
503 17
|
6月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
311 1
|
6月前
|
存储 JavaScript 前端开发
如何高效实现 vue 文件批量下载及相关操作技巧
在Vue项目中,实现文件批量下载是常见需求。例如文档管理系统或图片库应用中,用户可能需要一次性下载多个文件。本文介绍了三种技术方案:1) 使用`file-saver`和`jszip`插件在前端打包文件为ZIP并下载;2) 借助后端接口完成文件压缩与传输;3) 使用`StreamSaver`解决大文件下载问题。同时,通过在线教育平台的实例详细说明了前后端的具体实现步骤,帮助开发者根据项目需求选择合适方案。
610 0
|
6月前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
305 0