Vue3项目自学训练------音乐播放器(使用vueX管理项目)(十)

目录

一、歌词页上下滚动

二、CD页的歌词显示(只显示一行歌词)

 三、优化歌词数据

四、优化滑动效果


一、歌词页上下滚动

导入使用

<!-- 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();
  }
});

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.MUXIAO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值