網易云音樂項目

項目功能說明

  1. 可順序、單曲循環(huán)、隨機播放音樂
  2. 播放上一曲、下一曲音樂
  3. 歌詞的實時展示
  4. 歌曲滾動條實時
  5. 點擊歌曲添加到播放列表

項目技術

  • react
  • antd組件庫
  • 用react-routerconfig進行路由管理
  • Hooks的useState進行狀態(tài)管理;
  • 后端使用網易云API接口獲取數據
  • axios進行交互。

文件目錄結構說明

  • assets: 存放項目資源,包括css,img,font等
  • common:公共的數據、常量等
  • components:多個頁面間共享的組件
  • pages:劃分各個頁面
  • router:路由配置
  • services:網絡請求
  • store:redux合并
  • utils:js的工具
導入文件的規(guī)范
  1. 導入第三方模塊(最上)
  2. 功能性東西(網絡請求,actionCreators、utils)
  3. 導入組件

本項目使用React.memo()來幫助我們控制何時重新渲染組件。組件僅在它的 props 發(fā)生改變的時候進行重新渲染。通常來說,在組件樹中 React 組件,只要有變化就會走一遍渲染流程。但是通過 PureComponent 和 React.memo(),我們可以僅僅讓某些組件進行渲染,從而提升性能。PureComponent 用于類組件,React.memo()用于函數組件

css重置

安裝normalize.css yarn add normalize.css,并通過@import "~normalize.css";模塊化導入。

項目配置別名

  1. yarn add @craco/craco
  2. 在package.json內修改項目啟動配置


    原package.json

    修改后
  3. 在根目錄下創(chuàng)建craco.config.js添加配置
  4. 重新啟動該項目

API接口

網易云音樂接口.png

項目結構劃分

添加路由
  • yarn add react-router-dom
  • 使用yarn add react-router-config將所有路由配置統(tǒng)一管理
  • 使用renderRoutes函數完成配置
  • 網易云音樂使用HashRouter

AppHeader和AppFooter布局

  • 安裝yarn add style-components,編寫css樣式
  • 導航欄采用flex布局
  • 導航欄前三項為路由跳轉,后三項為超鏈接跳轉
  • 導航欄選項卡提前配置成數組數據,在local-config.js文件內導出,使用時只需要遍歷數組即可
  • 搜索框使用antdesign yarn add antd
    引入yarn add @ant-design/icons,使用圖標庫
    @import "~ant/dist/antd.css";在reset.css內引入

discover子路由的搭建和配備

  • discover文件夾下創(chuàng)建c-pages文件夾,放入子組件
  • 項目中引入axios發(fā)送網絡請求 yarn add axios

使用Hooks useState和axios獲取數據

使用antDesign組件制作輪播圖(走馬燈)

  • 輪播圖背景圖:高斯模糊圖片;是由原圖片拼接上"?imageView&blur=40x20"后得到。

輪播圖下內容制作

  • 將每個相似樣式的標題組件抽出到公共組件中,components文件夾下theme-header-rcm文件夾中
  • 熱門推薦中每個歌曲封面相同樣式也抽到公共組件中,components文件夾下songs-sover文件夾中
  • 對音樂播放量數字處理的函數getCount()寫在utils文件夾的format-utils.js文件內
  • 優(yōu)化頁面性能:圖片大小優(yōu)化(對獲取到的封面圖片大小處理的函數getSizeImage()也寫在utils文件夾的format-utils.js文件內)

新碟上架

  • 新碟上架的分頁效果采用輪播圖方法制作

榜單

  • 榜單的自定義組件也在components文件夾內的top-ranking文件內

播放音樂

  • 使用h5新增audio標簽播放音樂,通過增加點擊事件播放音樂;使用antd的滑動輸入條組件更改樣式模仿
   //播放音樂
    useEffect(() => {
        audioRef.current.src = getPlayUrl(song.id);
    }, [currentSong]);
  • 使用audio標簽中的onTimeUpdate={CurrentTime}屬性獲得當前音樂播放時間,利用useState()保存音樂進度時間
const [currentTime, setCurrentTime] = useState(0);
  const timeUpdate = (e) => {
        // console.log(e.target.currentTime);
        // e.target.currentTime(獲得當前播放音樂的進度時間) *1000  轉換為毫秒
        setCurrentTime(e.target.currentTime * 1000)
    }
  • 實時更新進度條:antd的滑動輸入條自帶value={progress}屬性,可以實時更新進度
 // 當前進度條進度
  const [progress, setProgress] = useState(0);
//寫在timeUpdate 回調函數內
  setCurrentTime(e.target.currentTime * 1000)
 //duration 為當前音樂的總時長(s)
  • 新增const [isChanging, setIsChanging] = useState(false);來判斷當前進度條是否正在改變,以便于當用戶正在播放音樂并滑動滾動條時,滾動條可以滑動

  • 利用antd的滑動輸入條自帶onChange和onAfterChange的API可以得到進度條滑動的位置和滑動后結束的位置


    antd滑動輸入條API
  • useCallback()使用:當把一個回調函數傳入到自定義組件內部的時候用其做一個包裹;目的是不希望子組件進行多次渲染;是為了進行性能的優(yōu)化

// 滑動中的位置
    const sliderChange = useCallback((value) => {
        setIsChanging(true);
        // 滑動滾動條時,實時更新時間的改變
        const currentTime = value /100 * duration ;
        setCurrentTime(currentTime)
        setProgress(value) ; 
        // console.log("change:",value);
        },[duration]);

   //滑動結束后的位置
    const sliderAfterChange = useCallback( (value) => {
        //互動進度條后的進度條時間
        const currentTime = value / 100 * duration /1000;
        audioRef.current.currentTime =  currentTime;
        // 重新更新進度條時間
        setCurrentTime(currentTime * 1000);
        setIsChanging(false);

        //如果沒有播放音樂,當滑動滾動條后開始播放音樂
        if(!isPlaying){
            playMusic();
        }

    }, [duration, isPlaying,playMusic] )
  • 播放和暫停播放
// 點擊事件
    const playMusic = () => {
        if(!isPlaying){ // 播放音樂
            audioRef.current.play();
            // console.log("播放");
        }
        else{   //暫停音樂
            audioRef.current.pause();
            // console.log("暫停");
        } 
        setIsPlaying(!isPlaying);
    }

音樂播放工具欄的實現

  • 邏輯為:當點擊推薦音樂的榜單組件內每個歌曲的播放圖標時,判斷該歌曲是否在播放列表內,如果不在則添加該歌曲到播放列表內并播放該歌曲;如果在則獲取該歌曲在列表中的位置,將其位置放在最頂端,并播放該歌曲。
  • 跨組件間的事件傳遞使用到了事件總線,安裝eventsyarn add events
    events常用的API:
    • 創(chuàng)建EventEmitter對象:eventBus對象;
    • 發(fā)出事件:eventBus.emit("事件名稱", 參數列表
    • 監(jiān)聽事件:eventBus.addListener("事件名稱", 監(jiān)聽函數);
    • 移除事件:eventBus.removeListener("事件名稱", 監(jiān)聽函數);

歌詞

  • 展示歌詞部分也使用的是antd的message組件
  • 通過api接口獲取到歌詞并解析展示

項目中出現的問題

  1. 背景圖片引入出現問題
    本來打算使用字符串拼接背景圖片地址,使用require()導入本地圖片
 background-image: url(${require("@/assets/img/banner_sprite.png")});

但是這樣無法獲取到本地圖片地址,我猜測是項目配置別名@craco使得無法使用@獲取到圖片地址。
我的解決辦法:

import url2 from '@/assets/img/banner_sprite.png'

background-image: url(${url2});
  1. 加載圖片
    使用useEffect()獲取信息,使用useState保存信息狀態(tài),并賦予他們初始狀態(tài)為[](空數組)。因為獲取到的信息仍然為一個數組,若要取得屬性,則要通過數組下標來獲取,但是打印空數組[0]為undefined,此時在獲取他的屬性就會報錯。
    我的解決辦法:
 const Song = currentSong[0] || "";
 const picUrl = (Song.al && Song.al.picUrl) || "";

若獲取到的值為undefine,則賦予“”。

  1. 播放音樂滑動進度條后,進度條不會立即到滑動的位置,而是會先回彈一下。
    原因:在獲取當前音樂播放時間時,利setCurrentTime(e.target.currentTime * 1000),但是e.target.currentTime無法更加實時的獲得當前滑動的數據,所以或出現上述bug。
    我的解決辦法:在滑動結束后的回調函數沖重新更新進度條時間
 //滑動結束后的位置
    const sliderAfterChange = useCallback( (value) => {
        //互動進度條后的進度條時間
        const currentTime = value / 100 * duration /1000;
        audioRef.current.currentTime =  currentTime;
        // 重新更新進度條時間
        setCurrentTime(currentTime * 1000);
        setIsChanging(false);
        }, [duration] )
  1. 播放音樂滑動進度條后,時間剛開始會隨著進度條的滑動而改變,轉而瞬間按照正在播放的音樂的時間變化。
    原因:當前時間沒有放進isChanging中,(啊哈哈哈哈哈笑死了)
    解決辦法:
// 控制當前進度條是否正在改變
    const [isChanging, setIsChanging] = useState(false);
//當前音樂播放時
    const timeUpdate = (e) => {
        // console.log(e.target.currentTime);
        // e.target.currentTime(獲得當前播放音樂的進度時間) *1000 轉換為毫秒
       
        if(!isChanging){
            setProgress(currentTime / duration * 100);
            setCurrentTime(e.target.currentTime * 1000);
        }
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容