目录
一、歌词页上下滚动
导入使用
<!-- middle-r -->
<my-scroll class="middle-r" :style="middleRStyle" ref="lyricScrollRef">
<div class="lyric-wrapper">
<div ref="lyricRef">
<p
class="text"
v-for="(item, index) in currentLyric"
:key="item.time"
:class="{ current: index === currentLineNum }"
>
{{ item.content }}
</p>
</div>
</div>
</my-scroll>
<script>
import MyScroll from "@/components/base/Scroll.vue"
const { currentLyric, lyricScrollRef, lyricRef, currentLineNum } = useLyric(currentTime);
</script>
useLyric.js(功能函数实现(使用定时器之前先清除定时器))
import { getLyric } from "@/service/song"
import { computed, ref, watch } from "vue"
import { useStore } from "vuex"
import { formatLyric } from "@/assets/js/utils";
export default function useLyric(currentTime) {
// 当前歌曲有变化,触发请求获取当前歌曲的歌词
const store = useStore()
const currentSong = computed(() => store.getters.currentSong)
const currentLyric = ref([])
//根据播放时间计算当前歌词列数
const currentLineNum = ref(0)
const lyricScrollRef = ref(null)
const lyricRef = ref(null)
const playingLyric = ref('')
let timer = null
watch(currentSong, async (newSong) => {
if (!newSong.id) return
const { lrc: { lyric } } = await getLyric(newSong)
// console.log(lyric);
currentLyric.value = formatLyric(lyric)
// 重置
currentTime.value = 0
stopLyric()
// 播放歌词
playLyric()
})
watch(currentLineNum, (newNum) => {
// 动画效果执行函数
run(newNum)
})
function run(newNum) {
// 滚动组件
const lyricScrollRefValue = lyricScrollRef.value
// 用于获取对应歌词p标签的div
const lyricRefValue = lyricRef.value
if (newNum > 5) {
// 触发页面滚动效果
const targetEl = lyricRefValue.children[newNum - 5]
lyricScrollRefValue.scroll.scrollToElement(targetEl, 1000)
} else {
// 如果index <= 5 就不发生滚动
lyricScrollRefValue.scroll.scrollTo(0, 0, 1000)
}
}
// 播放歌词
function playLyric() {
const currentLyricValue = currentLyric.value
if (currentLyricValue) {
// 计算当前的行数
countIndex()
// 开启歌词滚动的运动函数
startRun()
}
}
// 停止歌词
function stopLyric() {
clearTimeout(timer)
}
//歌词滚动
function startRun() {
const currentLyricValue = currentLyric.value
if (!currentLyricValue.length) return
// 获取当前的行数
let index = currentLineNum.value
// 如果已经是最后一句歌词,就不在往下开启定时器
if (index === currentLyricValue.length - 1) return
// 计算下一句歌词触发运行的定时器的延迟时间:下一句歌词的time - currentTime
let delay = currentLyricValue[index + 1].time - currentTime.value
timer = setTimeout(() => {
// 不能超过最后一句歌词的下标
currentLineNum.value = Math.min(++index, currentLyricValue.length - 1)
// 更新当前播放的歌词
playingLyric.value = currentLyricValue[currentLineNum.value].content
startRun()
}, delay * 1000)
}
function countIndex() {
// currentTime和歌词数组中的time的比较
let currentLyricValue = currentLyric.value
let currentTimeValue = currentTime.value
// 如果歌词数组为空
if (!currentLyricValue.length) return
let index = 0
for (let i = 0; i < currentLyricValue.length; i++) {
// 当前时间大于等于当前i的time,且小于下一个i的time,取i为当前歌曲的index
// 如果是最后一句歌词,只需要满足第一个条件即可
if (currentTimeValue >= currentLyricValue[i].time && currentLyricValue[i + 1] ? currentTimeValue < currentLyricValue[i + 1].time : true) {
index = i
break
}
}
// 当前行数
currentLineNum.value = index
playingLyric.value = currentLyricValue[index].content
}
return {
currentLyric,
lyricScrollRef,
lyricRef,
currentLineNum,
stopLyric,
playLyric,
playingLyric
}
}
通过按钮控制歌词
先导出
return {
currentLyric,
lyricScrollRef,
lyricRef,
currentLineNum,
stopLyric,
playLyric
}
后导入
const {
currentLyric,
lyricScrollRef,
lyricRef,
currentLineNum,
stopLyric,
playLyric,
} = useLyric(currentTime);
使用暂停或播放控制歌词
// 监听状态
watch(playing, (newPlaying) => {
let audio = audioRef.value;
if (newPlaying) {
// 播放
audio.play();
stopLyric();
playLyric();
} else {
// 暂停
audio.pause();
stopLyric();
}
});
拖动进度条歌词相对于变化
// 进度条变化后(拖动后松手)
function onProgressChanged(progress) {
progressChanging = false;
// 设置给audio真正去修改位置
audioRef.value.currentTime = progress * duration.value;
//修改播放状态
if (!playing.value) {
store.commit("setPlayingState", true);
}
stopLyric();
playLyric();
}
点击进度条歌词发生对应变化
// 进度条变化中
function onProgressChanging(progress) {
progressChanging = true;
currentTime.value = currentTime.value = progress * duration.value;
// 拖动滚动条歌词不发生变化
stopLyric();
}
当单曲循环的时候歌词变化
// 封装单曲循环
function loop() {
const audio = audioRef.value;
// currentTime:当前播放时间
audio.currentTime = currentTime.value = 0;
// 重新播放
audio.play();
store.commit("setPlayingState", true);
stopLyric();
playLyric();
}
二、CD页的歌词显示(只显示一行歌词)
const playingLyric = ref('')
playingLyric.value = currentLyricValue[index].content
// 更新当前播放的歌词
playingLyric.value = currentLyricValue[currentLineNum.value].content
return {
playingLyric
}
const { playingLyric } = useLyric(currentTime);
<div class="playing-lyric">{{ playingLyric }}</div>
三、优化歌词数据
export function formatLyric(str) {
/*
reduce((total, currentValue, currentIndex, arr) => {}, initiallyriValue)
@params:
total 必填项, 初始值,或者计算结束后的返回值
currentValue 必填项, 当前元素
currentIndex 可选项, 当前元素索引值
arr 可选项, 传递函数的初始值
initiallyriValue 可选项, 传递函数的初始值
*/
// 截取[并过滤空行(reduce简化)
let arr = str.split('[').filter(item => item !== '').reduce((total, currentValue) => {
let obj = {}
let time = currentValue.split(']')[0]//'01:20.80'
// console.log(time);
let timeArr = time.split(':')//['01', '20.80']
obj.time = Number(timeArr[0]) * 60 + Number(timeArr[1])
// console.log(obj.time);
obj.content = currentValue.split(']')[1]
// console.log(obj.content);
total.push(obj)
return total
}, []);
return arr.filter(item => item.content.trim() !== '')
}
四、优化滑动效果
const directionValue = ref('') //记录左右滑动还是上下滑动
锁定移动方向
// 手指落下
function onMiddleTouchStart(e) {
// 初始x坐标
touch.x1 = e.touches[0].pageX
// console.log(touch.x1);
touch.y1 = e.touches[0].pageY
// 方向锁定
touch.directionLocked = ''
}
// 手指移动
function onMiddleTouchMove(e) {
// 获取偏移量
const deltaX = e.touches[0].pageX - touch.x1
const deltaY = e.touches[0].pageY - touch.y1
const absDeltaX = Math.abs(deltaX) //取绝对值
const absDeltaY = Math.abs(deltaY) //取绝对值
// 判断方向
if(!touch.directionLocked) {
// 第一次进来锁定方向
touch.directionLocked = absDeltaX >= absDeltaY ? '水平' : '垂直'
}
if(touch.directionLocked === '垂直') {
directionValue.value = '垂直'
return
}
directionValue.value = '水平'
// 计算百分比: 偏移量绝对值 / 屏幕宽度
touch.percent = absDeltaX / window.innerWidth
// 初始操作CD页
if (currentView === 'cd') {
if (deltaX < 0) {
// 实现左滑,记录歌词页的左偏移量
touch.left = deltaX
// 手指滑动超过0.2比例时切换Lyric歌词页面
currentShow.value = touch.percent > 0.2 ? 'lyric' : 'cd'
middleLStyle.value = {
opacity: 1 - touch.percent
}
middleRStyle.value = {
Transform: `translate(${touch.left}px, 0)`
}
}
} else {
// 歌词页
if (deltaX > 0) {
// 右滑
touch.left = -window.innerWidth + deltaX
currentShow.value = touch.percent > 0.2 ? 'cd' : 'lyric'
middleLStyle.value = {
opacity: touch.percent
}
middleRStyle.value = {
Transform: `translate(${touch.left}px, 0)`
}
}
}
}
return {
directionValue
}
const {
directionValue
} = useLyric(currentTime);
watch(directionValue, (newDirection) => {
// console.log(newDirection);
if (newDirection === "垂直") {
// 恢复功能
lyricScrollRef.value.scroll.enable();
} else {
// 水平方向,禁用scroll,失去功能
lyricScrollRef.value.scroll.disable();
}
});