微信小程序页面结构和组件练习--音乐播放器
说明:
- 这个项目旨在练习组件以及页面的设计。页面和交互的实现可能有多种方式,这里只为了对组件和项目的结构进行熟悉了解。后续会有更加完善的项目。
- 由于涉及到mp3外链导入音乐,预览二维码在外链失效时会出错,这里不放预览二维码了;另预览二维码存在有效时长,有兴趣的可以自己试着运行程序,在运行程序之前更换mp3外链的地址,以防出错。mp3外链获取(音乐外链在线获取_音乐外链生成网站_音乐在线听-mp3外链网 (xf1433.com))。
- 喜欢的可以点个赞,如果有问题或者想要完整的项目代码及素材,底下评论留下联系方式或者私信也可。
效果图:
- 三个页面通过轮播图的方式进行切换并实现一定的交互效果。其中,音乐推荐页没有设定交互;播放器页的进度条具有滑动可改变进度;播放列表页具有播放列表音乐的事件;底部的显示区域具有控制音频的交互。
- 遇到的问题:布局嵌套;初始数据的访问;渲染的嵌套使用;
bug:
- 播放器页面的播放时长和进度条的显示:页面首次加载获取不到默认曲目的音频时长,进度条也无任何作用。按下一首和播放列表进行播放也不显示,按暂停后再按播放就能正常显示。
- 底部的三个图标在手机上预览时看不到图标,但是有具体的交互效果。
代码部分:
- 这里给出项目的代码,没给出代码却存在的文件内容为空。关于素材方面,icon和img分别是图片和图标文件夹;照片是平时拍的,图标在阿里巴巴图标库找的(iconfont-阿里巴巴矢量图标库),需要了底下评论留下联系方式。
app.json
{
"pages": [
"pages/index/index"
],
"window": {
"navigationBarBackgroundColor": "#e0e0e0",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "音乐"
},
"sitemapLocation": "sitemap.json"
}
index.wxml
<view class="tap"><!--导航区域-->
<view class="tap1" bindtap="onRecommend" data-item="0">音乐推荐</view>
<view class="tap1" bindtap="onPlay" data-item="1">播放器</view>
<view class="tap1" bindtap="onList" data-item="2">播放列表</view>
</view>
<view class="contain"><!--内容区域-->
<swiper current="{{item}}">
<swiper-item><include src="recommend.wxml" /></swiper-item>
<swiper-item><include src="play.wxml" /></swiper-item>
<swiper-item><include src="list.wxml" /></swiper-item>
</swiper>
</view>
<view class="footer"><!--底部区域-->
<view>
<view class="footer1">
<image src="{{playSing.musiccoverImg}}" class="footerImage"></image>
</view>
<view class="footer2">
<view>{{playSing.musicsong}}</view>
<view>{{playSing.musicsinger}}</view>
</view>
<view class="footer3">
<view>
<image src="../../icon/菜单.png" bindtap="menuTap" data-item="2"></image>
<view>
<image wx:if="{{!isPlayMusic}}" src="../../icon/暂停.png" bindtap="pauseTap"></image>
<image wx:else src="../../icon/播放.png" bindtap="playTap"></image>
</view>
<image src="../../icon/下一曲.png" bindtap="nextTap"></image>
</view>
</view>
</view>
</view>
index.wxss
page{
display: flex;flex-direction: column;background: rgb(146, 128, 121);height: 100%;
}
.tap{display: flex;
background-color: rgb(82, 240, 121);height: 70rpx;
}
.tap1{flex: 1;font-size: 20px;text-align:center;border-bottom: solid 5rpx rgb(50, 9, 235);}
.contain{flex:1;}
.contain>swiper{height:100%;background-color: rgb(42, 182, 147);}
.recom1{margin-bottom: 60rpx;height: 550rpx;}
.recom1 image{width: 100%;height: 100%;}
.recom2{display: flex;flex-wrap:wrap;margin-bottom: 20rpx;}
.recom2>view{flex: 1;text-align: center;}
.recom2 image{width: 120rpx;height: 100rpx;border: solid 4rpx rgb(120, 223, 197);border-radius: 10%;}
.recom3{display: flex;flex-wrap:wrap;flex: 1;margin-bottom: 20rpx;}
.recom3>view{text-align:center;margin: 10rpx 20rpx;}
.recom3 image{width: 210rpx;height: 140rpx;border-radius: 10%;}
.list{display: flex;flex: 1;flex-direction: column; }
.list>view{border-bottom: solid 3rpx rgb(24, 216, 223);display: flex;}
.list image{width: 100rpx;height: 100rpx;margin: 15rpx;}
.list2{flex: 1;display: flex;}
.list2>view:first-child{text-align: left;margin-top: 20rpx;}
.list2>view:last-child{flex:1;text-align: right;margin-top: 50rpx;margin-right: 20rpx;}
.play{height:100%;display: flex;flex-direction: column;justify-content: center;}
.play1{text-align: center;margin:20rpx;font-size: 40rpx;}
.play2{text-align: center;flex: 1;}
.play2 image{width: 350rpx;height:350rpx;border-radius: 50%;margin-top: 130rpx;}
.playrotate{animation: animateRotate 10s linear infinite;}
.play3{display: flex;}
.play3 slider{flex: 1;}
.play3>view:first-child{margin: 20rpx 0rpx 20rpx 20rpx;padding:0rpx;}
.play3>view:last-child{margin: 20rpx 20rpx 20rpx 0rpx;padding:0rpx;}
.footer{background-color:rgb(12, 228, 243);font-size: 18px;}
.footer>view{display: flex;}
.footerImage{width: 80rpx;height: 80rpx;margin: 15rpx;}
.footer2{display: flex;flex-direction: column;}
.footer2 view{text-align: center;margin: 5rpx;font-size: 30rpx;}
.footer3{flex: 1;}
.footer3>view{display: flex;justify-content: flex-end;padding-top: 20rpx;}
.footer3 image{width: 70rpx;height: 70rpx;margin: 0rpx 15rpx;}
@keyframes animateRotate{
from{transform: rotate(0deg);}
to{transform: rotate(360deg);}
}
index.js
Page({
data: {
item:0,
isactive:0,
isPlayMusic:false,
isrotate:false,
isplay:true,
listSing:[
{id:"1",song:"八月夜桂花",singer:"还潮",coverImg:"../../img/sw1.jpg",audioSrc:"https://sharefs.ali.kugou.com/202107232134/30280d96986673559df9ab931c6bc75b/G174/M02/0D/01/7g0DAF3n4iOAcc9TAD3rmaDghjI270.mp3"},
{id:"2",song:"晚风",singer:"陈婧霏",coverImg:"../../img/sw2.jpg",audioSrc:"https://sharefs.ali.kugou.com/202107232131/e87b792dd4206b1c082ed389d423512d/G164/M03/04/00/5A0DAF1c-AKAeVXaACsys46W7mQ528.mp3"},
{id:"3",song:"亲密爱人",singer:"梅艳芳",coverImg:"../../img/sw3.jpg",audioSrc:"http://mp.333ttt.com/mp3music/22851062.mp3"},
{id:"4",song:"在冬天和奶奶一起晒太阳",singer:"赵照",coverImg:"../../img/sw1.jpg",audioSrc:"https://sharefs.ali.kugou.com/202107232134/7a25da194582ddcf4cf6f856701b50e5/G189/M0A/15/18/nZQEAF5ZEk6AfipaAEPqyN-VYhI072.mp3"},
{id:"5",song:"晚安",singer:"林宥嘉",coverImg:"../../img/sw3.jpg",audioSrc:"https://sharefs.ali.kugou.com/202107222148/67074aa123d09ee52aa715578b487694/KGTX/CLTX001/c59c04254efa6f54d7bdd675947f68ac.mp3"},
{id:"6",song:"Sofia",singer:"Alvaro Soler",coverImg:"../../img/sw2.jpg",audioSrc:"https://sharefs.ali.kugou.com/202107231545/a70098ef00c6fdbb3722f0c0a32a12e7/G201/M01/0A/04/aYcBAF5ABeiAL5WOADNmeboY3wg848.mp3"},
{id:"7",song:"Wild",singer:"Monogem",coverImg:"../../img/sw1.jpg",audioSrc:"https://sharefs.ali.kugou.com/202107231546/2a37951e34e374ad35749dcefa39f28e/G119/M04/04/17/tw0DAFpOGEOAKYizADS68iNCyy4692.mp3"},
{id:"8",song:"童话镇",singer:"暗杠",coverImg:"../../img/sw3.jpg",audioSrc:"https://sharefs.ali.kugou.com/202107231550/275d29499eba4450e5751bba98ad2563/G062/M03/14/11/fg0DAFd1IvSAJlbkAD0PMS2Gwek596.mp3"}
],
playSing:{
musicsong:'',
musicsinger:'',
musiccoverImg:'',
currenttime:'00:00',
totaltime:'00:00',
percent:0
}
},
//可直接访问并赋值属性
audio:null,
indexlist:0,
musicnum:0,
//顶部栏事件
onRecommend:function(e){
this.setData({item:e.target.dataset.item,isactive:0})
},
onPlay:function(e){this.setData({item:e.target.dataset.item,isactive:1})},
onList:function(e){this.setData({item:e.target.dataset.item,isactive:2})},
//recommend页面事件
//play页面事件
onMusic:function(index){
var music = this.data.listSing[index];
this.audio.src=music.audioSrc;
this.setData({
'playSing.musicsong':music.song,
'playSing.musicsinger':music.singer,
'playSing.musiccoverImg':music.coverImg,
'playSing.totaltime':'00:00',
'playSing.currenttime':'00:00'
})
},
onSlider:function(e){
this.audio.seek(e.detail.value/100*this.audio.duration)
this.setData({
'playSing.currenttime':formatTime(e.detail.value/100*this.audio.duration),
'playSing.totaltime':formatTime(this.audio.duration),
'playSing.percent':this.audio.currentTime/this.audio.duration*100
})
function formatTime(time){
var minute=Math.floor(time/60)%60;
var second=Math.floor(time)%60;
return (minute<10? '0'+minute : minute) + ':' + (second<10? '0'+second:second)
}
},
//底部播放栏事件
menuTap:function(e){
this.setData({item:e.target.dataset.item})
},
pauseTap:function(){
this.onAuxiliary()
this.audio.play()
},
playTap:function(){
this.audio.pause()
this.setData({isPlayMusic:false,isrotate:false})
},
nextTap:function(){
if(this.musicnum <= (this.indexlist+1)){
this.indexlist=0;
this.onMusic(this.indexlist) //音乐信息
this.onAuxiliary() //音乐辅助事件
this.audio.play() //播放音乐
return
}
this.onMusic(++this.indexlist)
this.onAuxiliary()
this.audio.play()
},
//list页面事件
musicList:function(e){
//这里对应底部的播放器的变化,同时右边当前播放显示,且音乐播放。
this.onMusic((e.currentTarget.dataset.listorder)-1)
this.indexlist=(e.currentTarget.dataset.listorder)-1;
this.onAuxiliary()
this.audio.play()
},
//辅助函数
onAuxiliary:function(){ //此辅助函数为添加播放的事件监听
this.setData({isPlayMusic:true,isrotate:true})
this.audio.onError(()=>{wx.showToast({ /*在播放的界面里写错误和结束事件 */
title: '播放地址错误'
})})
this.audio.onEnded(()=>{
this.nextTap()
})
/*使用播放事件来时刻显示播放进度 */
// this.audio.onPlay(()=>{ })
this.audio.onTimeUpdate(()=>{
this.setData({
'playSing.totaltime':formatTime(this.audio.duration),
'playSing.currenttime':formatTime(this.audio.currentTime),
'playSing.percent':this.audio.currentTime/this.audio.duration*100
})
})
function formatTime(time){
var minute=Math.floor(time/60)%60;
var second=Math.floor(time)%60;
return (minute<10? '0'+minute : minute) + ':' + (second<10? '0'+second:second)
}
},
})
recommend.wxml
<scroll-view class="recom" scroll-y="true" style="height:100%;">
<swiper class="recom1" indicator-dots="true" autoplay circular>
<swiper-item><image src="../../img/sw1.jpg" mode="aspectFill"></image></swiper-item>
<swiper-item><image src="../../img/sw2.jpg" mode="aspectFill"></image></swiper-item>
<swiper-item><image src="../../img/sw3.jpg" mode="aspectFill"></image></swiper-item>
</swiper>
<view class="recom2">
<view>
<image src="../../img/sw1.jpg"></image>
<view>私人FM</view>
</view>
<view>
<image src="../../img/sw11.jpg"></image>
<view>每日歌曲推荐</view>
</view>
<view>
<image src="../../img/sw3.jpg"></image>
<view>云音乐新歌榜</view>
</view>
</view>
<view style="margin-left:20rpx;margin-bottom:10rpx;">热门音乐</view>
<view class="recom3">
<view class="recom3_1">
<image src="../../img/sw11.jpg"></image>
<view>紫罗兰</view>
</view>
<view class="recom3_1">
<image src="../../img/sw11.jpg"></image>
<view>五月之家</view>
</view>
<view class="recom3_1">
<image src="../../img/sw11.jpg"></image>
<view>菩提树</view>
</view>
<view class="recom3_1">
<image src="../../img/sw11.jpg"></image>
<view>紫罗兰</view>
</view>
<view class="recom3_1">
<image src="../../img/sw11.jpg"></image>
<view>五月之家</view>
</view>
<view class="recom3_1">
<image src="../../img/sw11.jpg"></image>
<view>菩提树2</view>
</view>
</view>
</scroll-view>
play.wxml
<view class="play">
<view class="play1">
<view>{{playSing.musicsong}}</view>
<view>--{{playSing.musicsinger}}--</view>
</view>
<view class="play2">
<image src="{{playSing.musiccoverImg}}" class="{{isrotate?'playrotate':'pauserotate'}}"></image>
</view>
<view class="play3">
<view>{{playSing.currenttime}}</view>
<slider block-size="12" value="{{playSing.percent}}" bindchange="onSlider"></slider>
<view>{{playSing.totaltime}}</view>
</view>
</view>
list.wxml
<scroll-view scroll-y="true" style="height:100%">
<view class="list">
<view wx:for="{{listSing}}" wx:key="id" wx:for-item="item1">
<view class="list1">
<image src="{{item1.coverImg}}" mode="aspectFill"></image>
</view>
<view bindtap="musicList" class="list2" data-listorder="{{item1.id}}">
<view class="list3">
<view>{{item1.song}}</view>
<view>{{item1.singer}}</view>
</view>
<view class="list3 list3-{{isplaycolor ? 'iscolor' : ''}}"><view wx:if="{{true}}">点击播放</view></view>
</view>
</view>
</view>
<view style="text-align:center;font-size:40rpx;">待更新~</view>
</scroll-view>