Vue开发历程---音乐播放器

简介: Vue开发历程---音乐播放器

前言

浅浅记录一下自己开发音乐播放器的历程,巩固自己的所学。同时也是深感基础不牢,地动山摇。


一、audio标签的使用

1、Audio 对象属性

属性 描述
audioTracks 返回表示可用音频轨道的 AudioTrackList 对象。
autoplay 设置或返回是否在就绪(加载完成)后随即播放音频。
buffered 返回表示音频已缓冲部分的 TimeRanges 对象。
controller 返回表示音频当前媒体控制器的 MediaController 对象。
controls 设置或返回音频是否应该显示控件(比如播放/暂停等)。
crossOrigin 设置或返回音频的 CORS 设置。
currentSrc 返回当前音频的 URL。
currentTime 设置或返回音频中的当前播放位置(以秒计)。
defaultMuted 设置或返回音频默认是否静音。
defaultPlaybackRate 设置或返回音频的默认播放速度。
duration 返回音频的长度(以秒计)。
ended 返回音频的播放是否已结束。
error 返回表示音频错误状态的 MediaError 对象。
loop 设置或返回音频是否应在结束时再次播放。
mediaGroup 设置或返回音频所属媒介组合的名称。
muted 设置或返回是否关闭声音。
networkState 返回音频的当前网络状态。
paused 设置或返回音频是否暂停。
playbackRate 设置或返回音频播放的速度。
played 返回表示音频已播放部分的 TimeRanges 对象。
preload 设置或返回音频的 preload 属性的值。
readyState 返回音频当前的就绪状态。
seekable 返回表示音频可寻址部分的 TimeRanges 对象。
seeking 返回用户当前是否正在音频中进行查找。
src 设置或返回音频的 src 属性的值。
textTracks 返回表示可用文本轨道的 TextTrackList 对象。
volume 设置或返回音频的音量。

2、对象方法

方法 描述
addTextTrack() 向音频添加新的文本轨道。
canPlayType() 检查浏览器是否能够播放指定的音频类型。
fastSeek() 在音频播放器中指定播放时间。
getStartDate() 返回新的 Date 对象,表示当前时间线偏移量。
load() 重新加载音频元素。
play() 开始播放音频。
pause() 暂停当前播放的音频。

二、效果

效果如下:

效果展示的Gif

三、代码

代码如下:
MusicPlayer.vue

<template>
    <div class="music">
        <!-- 占位 -->
        <div class="m_hold">

        </div>
        <div class="m_img">
            <img :src="this.$parent.songNames[this.$parent.index].png" width="90px" :class="this.$parent.isRun">
        </div>
        <!-- 歌曲信息 -->
        <div class="m_text">
            {{ this.$parent.songNames[this.$parent.index].name }}
            <div class="block" style="margin-top:5px">
                <el-slider :v-model="value1"></el-slider>
            </div>
        </div>
        <!-- 按钮 -->
        <div class="m_btn">
            <a href="#" class="m_prev" @click="playLastSong()"></a>
            <a href="#" class="m_play" @click="changeState()" v-show="this.$parent.isShow"></a>
            <a href="#" class="m_pause" @click="changeState()" v-show="!this.$parent.isShow"></a>
            <a href="#" class="m_next" @click="playNextSong()"></a>
        </div>
        <!-- 折叠功能 -->
        <div class="m_close" @click="changeCloseState()">
            <a href=""></a>
        </div>

    </div>
</template>

<script>
export default {
    name: 'MusicPlayer',
    data() {
        return {
            songName: '',
            value1:0

        }
    },
    methods: {
        changeState() {

            this.$emit("play")
        },
        changeCloseState() {
            this.$emit("hello");
        },
        playNextSong() {
            this.$emit("nextSongs");
            this.songName = this.$parent.songNames[this.$parent.index].name
        },
        playLastSong() {
            this.$emit("lastSongs");
            this.songName = this.$parent.songNames[this.$parent.index].name
        }
    },
    watch:
    {
       

    }, mounted() {
        this.songName = this.$parent.songNames[this.$parent.index].name
    }

}
</script>

<style scoped>
/* 关于播放器的样式 */
.music {
    width: 100%;
    height: 120px;
    background: black;
    /* 相对浏览器定位 */
    position: absolute;
    left: 0px;
    bottom: 100px;
    border-bottom: 50px;
    /* 透明度 */
    opacity: 0.8;
    /* 阴影值 */
    box-shadow: 10px 15px 15px 1px black
}

.music .m_hold {
    float: left;
    width: 90px;
    height: 90px;
}

/* 调整音乐盒图片 */
.music .m_img {
    margin-top: 15px;
    margin-left: 10px;
    margin-right: 10px;
    /* 左浮动 */
    float: left;
    width: 90px;
    height: 90px;
    border-radius: 50%;
    overflow: hidden;

}

/* 修改文字 */
.music .m_text {
    /* 左浮动 */
    float: left;
    color: white;
    font-size: 20px;
    /* 字体加粗 */
    font-weight: bold;
    margin-top: 25px;
    margin-left: 20px;
    margin-bottom: 10px;
    width: 25%;

}

/* 使得所有a标签一起移动 */
.music .m_btn {
    float: left;
    position: absolute;
    /* 绝对定位:防止歌曲名称过长,挤出div */
    left: 40%;
}

/* 修改a标签 */
.music .m_btn a {
    width: 32px;
    height: 32px;
    float: left;
    margin-top: 50px;
    margin-left: 20px;
    background: url(@/assets/player_bg.png);

}

.music .m_btn .m_prev {
    background-position: -69px 0px;
}

.music .m_btn .m_next {
    background-position: -150px 0px;
}

.music .m_btn .m_play {
    background-position: -107px -5px;
}

.music .m_btn .m_prev:hover {
    background-position: -69px -32px;
}

.music .m_btn .m_next:hover {
    background-position: -150px -32px;
}

.music .m_btn .m_play:hover {
    background-position: -107px -47px;
}

.music .m_btn .m_pause {
    background-position: -292px -94px;
}

.music .m_btn .m_pause:hover {
    background-position: -334px -94px;
}

/* 还有一个悬停 没写 */
/* 设置最右边的关闭样式 */
.music .m_close {
    float: right;
    background: white;
    cursor: pointer;
    width: 23px;
    height: 100px;
    margin-top: 10px;
    background: url(@/assets/player_bg.png);

}

/* 设置最右边的关闭样式 */
.music_hide {
    float: left;
    background: white;
    cursor: pointer;
    width: 23px;
    height: 100px;
    margin-top: 2px;
}

.go {
    animation: bounce-in 2s linear infinite;
}

.come {
    animation: none;
}

@keyframes bounce-in {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

.open-enter-active {
    animation: slide-in linear 0.5s;
}

.open-leave-active {
    animation: slide-in reverse linear 0.5s;
}

@keyframes slide-in {
    from {
        transform: translateX(-100%);
    }

    to {
        transform: translateX(0%);
    }
}
</style>

HideMusic.vue

<template>
    <div class="music_hide" @click="changeCloseState()"><a href="#" class="m_open"></a></div>
</template>

<script>
export default {
    name:'HidePlayer',
    methods:{
        changeCloseState()
        {
            this.$emit("hello");
        }
    }
}
</script>

<style scoped>
.music_hide {
    float: left;
    background: url(@/assets/player_bg.png);
    cursor: pointer;
    width: 23px;
    height: 100px;
    margin-top: 10px;
    bottom: 100px;
    position: absolute;
    background-position-x: -45px;
}
</style>

MyPlayer.vue

<template>
    <div>
        <transition name="open" mode="out-in">
            <component v-bind:is="view" @hello="changeSlideState" @play="changePlayState" @lastSongs="lastSongs"
                @nextSongs="nextSongs"></component>
        </transition>
        <audio class="m_mp3" id="m_mp3" :src="this.songNames[this.index].Url" autoplay loop>

        </audio>
    </div>

</template>

<script>
import HidePlayer from '@/part/HidePlayer'
import MusicPlayer from '@/part/MusicPlayer'
export default {
    name: 'MyPlayer',
    data() {
        return {
            view: MusicPlayer,
            isClose: false,
            isShow: true,
            isRun: 'come',
            index: 0,
            songNum: 2,
            currentTime: '0:00',
            duration: '0:00',
            songNames: [
                {
                    id: 1,
                    name: '张韶涵-篇章',
                    Url: require('@/assets/张韶涵-篇章.mp3'),
                    png: require('@/assets/篇章.png'),
                },
                {
                    id: 2,
                    name: '爱就一个字 抒情版',
                    Url: require('@/assets/爱就一个字 抒情版.mp3'),
                    png: require('@/assets/爱就一个字.png'),
                },
                {
                    id: 3,
                    name: '最伟大的作品-周杰伦',
                    Url: require('@/assets/最伟大的作品-周杰伦.mp3'),
                    png: require('@/assets/周杰伦.jpg'),
                },
                {
                    id: 4,
                    name: '等你下课 (with 杨瑞代)-周杰伦',
                    Url: require('@/assets/等你下课 (with 杨瑞代)-周杰伦.mp3'),
                    png: require('@/assets/等你下课.png'),
                },
                {
                    id: 5,
                    name: '告白气球-周杰伦',
                    Url: require('@/assets/告白气球-周杰伦.mp3'),
                    png: require('@/assets/告白气球.png'),
                },
                {
                    id: 6,
                    name: '还在流浪-周杰伦',
                    Url: require('@/assets/还在流浪-周杰伦.mp3'),
                    png: require('@/assets/还在流浪.png'),
                },
            ]
        }
    },
    components: {
        HidePlayer,
        MusicPlayer
    },
    methods: {
        changeSlideState() {
            this.isClose = !this.isClose;
            if (this.isClose) {
                this.view = HidePlayer;
            } else {
                this.view = MusicPlayer;
            }
        },
        changePlayState() {
            if (!this.isShow) {
                this.isShow = true;
                this.isRun = "come";
                document.getElementById("m_mp3").pause();
            } else {
                this.isShow = false;
                this.isRun = "go";
                var my_mp3 = document.getElementById("m_mp3");
                my_mp3.play();


            }
        },
        nextSongs() {
            if (this.isShow) {
                this.isShow = false;
                this.isRun = "go";
            }
            this.index = (this.index + 1) % this.songNum;
        },
        lastSongs() {
            if (this.isShow) {
                this.isShow = false;
                this.isRun = "go";
            }
            if (this.index == 0) {
                this.index = this.songNum - 1;
            } else {
                this.index = this.index - 1;
            }

        }
    }, mounted() {
        this.songNum = this.songNames.length;
        
    }

}
</script>

<style scoped>
.open-enter-active {
    animation: slide-in linear 0.5s;
}

.open-leave-active {
    animation: slide-in reverse linear 0.5s;
}

@keyframes slide-in {
    from {
        transform: translateX(-100%);
    }

    to {
        transform: translateX(0%);
    }
}
</style>

四、难点解析


1、过渡动画的实现

动画切换
参考了vue文档过渡&动画中多个组件的过渡(下面三份代码)。vue文档

<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>
new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})
.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active for below version 2.1.8 */ {
  opacity: 0;
}

因此分化出MusicPlayer.vue 和 HideMusic.vue,由此又产生了组件内通信的问题。

2、组件内通信

原因
为什么会产生组件内的通信?原因在于:MusicPlayer组件和HidePlayer组件,只能有一个展示,但是在不展示的过程中,他的数据应该也是实时改变的。例如MusicPlayer组件上有播放按钮,如果不采用组件通信,那么MusicPlayer重新渲染的时候,播放按钮会回到最初的设定,是不符合逻辑的。所以需要采用组件内通信的方式。实现的方式也比较简单,子组件直接访问父组件的数据,子组件通过$emit调用父组件的方法,修改父组件的数据。

3、旋转动画的实现

图片旋转
首先,编写动画。

.go {
    animation: bounce-in 2s linear infinite;
}

.come {
    animation: none;
}

@keyframes bounce-in {
    from {
        transform: rotate(0deg);
    }

    to {
        transform: rotate(360deg);
    }
}

然后,动态绑定class,isRun两个值即为"go","come"。

<div class="m_img">
   <img :src="this.$parent.songNames[this.$parent.index].png" width="90px" :class="this.$parent.isRun">
</div>

总结

这次一个小小的实战,让我重新学习到了不少的CSS操作方法,但由于基础太弱,很多简单的东西都变得复杂。还好vue提供了一些方法,让解决方式变得容易了一些。奋斗吧!还有很长的路要走!!

相关文章
|
22天前
|
JavaScript
vue使用iconfont图标
vue使用iconfont图标
119 1
|
1天前
|
JavaScript 安全 API
iframe嵌入页面实现免登录思路(以vue为例)
通过上述步骤,可以在Vue.js项目中通过 `iframe`实现不同应用间的免登录功能。利用Token传递和消息传递机制,可以确保安全、高效地在主应用和子应用间共享登录状态。这种方法在实际项目中具有广泛的应用前景,能够显著提升用户体验。
22 8
|
2天前
|
存储 设计模式 JavaScript
Vue 组件化开发:构建高质量应用的核心
本文深入探讨了 Vue.js 组件化开发的核心概念与最佳实践。
17 1
|
1月前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。
|
2月前
|
JavaScript API 开发者
Vue是如何进行组件化的
Vue是如何进行组件化的
|
2月前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱前端的大一学生,专注于JavaScript与Vue,正向全栈进发。博客分享Vue学习心得、命令式与声明式编程对比、列表展示及计数器案例等。关注我,持续更新中!🎉🎉🎉
57 1
vue学习第一章
|
2月前
|
JavaScript 前端开发 索引
vue学习第三章
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中的v-bind指令,包括基本使用、动态绑定class及style等,希望能为你的前端学习之路提供帮助。持续关注,更多精彩内容即将呈现!🎉🎉🎉
53 1
|
2月前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
47 1
vue学习第四章
|
2月前
|
JavaScript 前端开发 算法
vue学习第7章(循环)
欢迎来到瑞雨溪的博客,一名热爱JavaScript和Vue的大一学生。本文介绍了Vue中的v-for指令,包括遍历数组和对象、使用key以及数组的响应式方法等内容,并附有综合练习实例。关注我,将持续更新更多优质文章!🎉🎉🎉
41 1
vue学习第7章(循环)
|
2月前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
47 1
vue学习第九章(v-model)