React模仿網(wǎng)易云音樂(lè)實(shí)現(xiàn)一個(gè)音樂(lè)項(xiàng)目詳解流程
一、項(xiàng)目功能說(shuō)明
- 暫停、播放歌曲
- 切換上一首、下一首歌曲
- 拖動(dòng)進(jìn)度條改變播放進(jìn)度
- 隨機(jī)播放、循環(huán)播放、單曲循環(huán)
- 實(shí)時(shí)展示歌詞
- 切換不同分類(lèi)的歌單、歌手、電臺(tái)
- 實(shí)現(xiàn)推薦、排行榜、歌單、主播電臺(tái)、歌手、新碟上架板塊的展示
二、最終效果
首頁(yè):

排行榜:

歌單:

主播電臺(tái):

歌手:

新碟上架:

三、文件目錄結(jié)構(gòu)說(shuō)明

- assets:存放共用的css、font圖標(biāo)、image
- common:存放共用的資源,如數(shù)據(jù)、常量
- components:存放多個(gè)頁(yè)面共享的組件
- pages:劃分各個(gè)頁(yè)面
- router:路由配置
- services:網(wǎng)絡(luò)請(qǐng)求
- store:合并所有reducer
- utils:一些js的工具
四、項(xiàng)目技術(shù)棧
- React 作為前端框架
- Ant Design 作為前端UI框架
- Redux 進(jìn)行狀態(tài)管理
- Axios 進(jìn)行網(wǎng)絡(luò)請(qǐng)求
- 通過(guò)調(diào)用網(wǎng)易云的API來(lái)獲取數(shù)據(jù)
- 使用 react-router-dom 的 Route, Switch 管理路由
- 使用普通css 及 styled-component 編寫(xiě) CSS
五、核心技術(shù)
- 配置項(xiàng)目別名:@craco/craco
craco還可以進(jìn)行wepback 進(jìn)行自定義配置、antd 組件按需加載、支持 less等操作。不選擇 npm run eject 是因?yàn)閑ject是不可逆操作。
安裝:npm i @craco/craco
在package.json中的配置:
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
根目錄下創(chuàng)建 craco.config.js
重啟項(xiàng)目
- 使用reset.css進(jìn)行 css 重置
通過(guò) import './assets/css/reset.css'引入。但 reset.css 存在一個(gè)很大的問(wèn)題是它將所有的瀏覽器的默認(rèn)樣式清除,從而達(dá)到所有瀏覽器樣式的統(tǒng)一的目的,但這么操作會(huì)導(dǎo)致瀏覽器原本的默認(rèn)樣式失去意義。比起 reset.css 我更傾向于使用 normalize.css 來(lái)統(tǒng)一樣式, normalize.css 是在盡量保留瀏覽器的默認(rèn)樣式的基礎(chǔ)上,不進(jìn)行太多的重置,這樣保留了有價(jià)值的默認(rèn)值,它還可以模塊化引入、修復(fù)了瀏覽器的一些 bug、沒(méi)有復(fù)雜的繼承鏈。
- 使用CSS Sprites 精靈圖
本次項(xiàng)目很多地方使用了CSS Sprites 精靈圖技術(shù),可以有效減少圖片的請(qǐng)求次數(shù),優(yōu)化性能。
- 使用 memo 包裹函數(shù)式組件,減少渲染次數(shù)
React.memo是一個(gè)高階函數(shù),與類(lèi)組件里面的 PureComponent類(lèi)似,它傳遞一個(gè)組件進(jìn)去,返回一個(gè)可以記憶的組件,在 props 不變的情況下,這個(gè)被包裹的組件是不會(huì)重新渲染的,這樣就減少了 render 的渲染次數(shù),從而提高了性能。
export default memo(function App() {
return (
<div className="App">
</div>
)
})
- AppHeader以及AppFooter的布局實(shí)現(xiàn)
使用 styled-components 編寫(xiě)css樣式,通過(guò) import styled from 'styled-components'引入css 文件
導(dǎo)航欄使用flex布局
導(dǎo)航欄前三項(xiàng)采用 路由跳轉(zhuǎn)、后三項(xiàng)是鏈接跳轉(zhuǎn)
搜索欄使用了 antd 的樣式,通過(guò) import { Input } from 'antd'引入
搜索圖標(biāo)使用了antd 的圖標(biāo)庫(kù),通過(guò)import { SearchOutlined } from '@ant-design/icons'引入
- 推薦頁(yè)輪播圖采用 antd 的 Carousel走馬燈組件完成
背景的模糊效果使用高斯模糊,通過(guò)改變請(qǐng)求的url ?imageView&blur=40x20 實(shí)現(xiàn),css可以用filter: blur(20px);實(shí)現(xiàn)
- 自定義公共組件,實(shí)現(xiàn)組件的復(fù)用
以下內(nèi)容都被抽取到 components目錄中。



- 對(duì)于圖片及播放次數(shù)也做了格式化處理
圖片的處理:在 utils 目錄的 format-utils 下編寫(xiě) getSizeImage 函數(shù),根據(jù)輸入的參數(shù)來(lái)決定圖片的大小
對(duì)數(shù)字的處理:在 utils 目錄的 format-utils 下編寫(xiě) getCount 函數(shù),格式化歌曲播放數(shù)量,讓用戶可以更直觀的知道播放次數(shù)

- 播放音樂(lè)板塊
播放/暫停音樂(lè):采用了 html5 的 audio 標(biāo)簽實(shí)現(xiàn),通過(guò)點(diǎn)擊播放/暫停按鈕實(shí)現(xiàn)歌曲的播放或暫停,通過(guò)isPlaying來(lái)獲取當(dāng)前播放狀態(tài),從而實(shí)現(xiàn)狀態(tài)切換。
// 播放/暫停音樂(lè)
const playMusic = useCallback(() => {
isPlaying ? audioRef.current.pause() : audioRef.current.play()
setIsPlaying(!isPlaying)
}, [isPlaying])
通過(guò) antd 的 Slider滑動(dòng)條來(lái)改變播放進(jìn)度:通過(guò)Slider自帶 value={progress} 屬性獲取當(dāng)前播放進(jìn)度,從而設(shè)置audio的播放進(jìn)度。
//進(jìn)度改變觸發(fā)onchange時(shí)間,調(diào)用該函數(shù)
const sliderChange = useCallback((value) => {
setIsChanging(true)
const currentTime = value / 100 * duration / 1000
setCurrentTime(currentTime * 1000)
setProgress(value)
}, [duration])
獲取當(dāng)前狀態(tài),切換播歌類(lèi)型:
// 獲取狀態(tài)中的播放類(lèi)型
sequence: state.getIn(['player', 'sequence']),
// 點(diǎn)擊切換播放類(lèi)型
<button className="sprite_playbar btn loop" onClick={changeSequence}></button>
// 改變播放類(lèi)型
const changeSequence = () => {
let currentSequence = sequence + 1
if (currentSequence > 2) { currentSequence = 0 }
dispatch(changeSequenceAction(currentSequence))
}使用 isChanging, setIsChanging = useState(false),來(lái)判斷當(dāng)前進(jìn)度條是否正在改變,以便于當(dāng)用戶正在播放音樂(lè)并滑動(dòng)滾動(dòng)條時(shí),滾動(dòng)條可以滑動(dòng)
利用 antd 的 Slider組件 自帶的onChange和onAfterChange的屬性可以得到進(jìn)度條滑動(dòng)的位置和滑動(dòng)后結(jié)束的位置
使用useCallback減少渲染次數(shù):
原理:把函數(shù)以及依賴項(xiàng)作為參數(shù)傳入 useCallback,它將返回該回調(diào)函數(shù)的 memoized 版本,這個(gè) memoizedCallback 只有在依賴項(xiàng)有變化的時(shí)候才會(huì)更新。
例如:
// 播放歌曲進(jìn)度條部分
// 滑動(dòng)中的位置
const sliderChange = useCallback((value) => {
setIsChanging(true)
// 滑動(dòng)滾動(dòng)條時(shí),實(shí)時(shí)更新時(shí)間的改變
const currentTime = value / 100 * duration / 1000
setCurrentTime(currentTime * 1000)
setProgress(value)
}, [duration])
// 滑動(dòng)后的位置
const sliderAfterChange = useCallback((value) => {
//滑動(dòng)進(jìn)度條后的進(jìn)度條時(shí)間
const currentTime = value / 100 * duration / 1000
audioRef.current.currentTime = currentTime
// 重新更新進(jìn)度條時(shí)間
setCurrentTime(currentTime * 1000)
setIsChanging(false)
//如果沒(méi)有播放音樂(lè),當(dāng)滑動(dòng)滾動(dòng)條后開(kāi)始播放音樂(lè)
if (!isPlaying) {
playMusic()
}
}, [duration, isPlaying, playMusic])
- 歌詞的處理
實(shí)現(xiàn):展示歌詞部分使用的是 antd的 Message全局提示
原理:先獲取到這首歌的全部歌曲,在 utils/parse-lyric下格式化歌詞,原理就是將字符串轉(zhuǎn)為數(shù)組,數(shù)組的每一項(xiàng)為 一個(gè)對(duì)象 { time, content }包含了時(shí)間及該時(shí)間的歌詞。
六、遇到的問(wèn)題
背景圖片不能正常顯示:
源代碼:
background-image: url(${require("@/assets/img/recommend-top-bg.png")});
解決方法:為圖片添加.default
background-image: url(${<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->require("@/assets/img/recommend-top-bg.png").default});編寫(xiě)代碼過(guò)程中無(wú)法將數(shù)據(jù)存儲(chǔ)到redux的state中,但是看了react devtools 發(fā)現(xiàn)代碼能夠正常執(zhí)行到action部分,在我編寫(xiě)了兩遍 actionCreators.js 和 reducer.js 后發(fā)現(xiàn)是因?yàn)槲覜](méi)有在總的 store 中合并 reducer,留下悲傷的淚水。
滑動(dòng)播放音樂(lè)的進(jìn)度條時(shí),進(jìn)度條會(huì)短暫的回彈到滑動(dòng)前位置。錯(cuò)誤原因:在獲取當(dāng)前音樂(lè)播放時(shí)間時(shí),利用setCurrentTime(e.target.currentTime * 1000),但是e.target.currentTime無(wú)法更加實(shí)時(shí)的獲得當(dāng)前滑動(dòng)的數(shù)據(jù),所以出現(xiàn)回彈。 解決方法:在滑動(dòng)結(jié)束后的回調(diào)函數(shù)沖重新更新進(jìn)度條時(shí)間。
const sliderAfterChange = useCallback((value) => {
// 獲取滑動(dòng)進(jìn)度條后的進(jìn)度條時(shí)間
const currentTime = value / 100 * duration / 1000
// 設(shè)置當(dāng)前時(shí)間
audioRef.current.currentTime = currentTime
setCurrentTime(currentTime * 1000)
}, [duration])
七、github鏈接
react仿寫(xiě)網(wǎng)易云音樂(lè)項(xiàng)目
到此這篇關(guān)于React模仿網(wǎng)易云實(shí)現(xiàn)一個(gè)音樂(lè)項(xiàng)目詳解流程的文章就介紹到這了,更多相關(guān)React網(wǎng)易云音樂(lè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
代碼解析React中setState同步和異步問(wèn)題
前端框架從MVC過(guò)渡到MVVM。從DOM操作到數(shù)據(jù)驅(qū)動(dòng),一直在不斷的進(jìn)步著,本文給大家介紹React中setState同步和異步問(wèn)題,感興趣的朋友一起看看吧2021-06-06
ForwardRef?useImperativeHandle方法demo
這篇文章主要為大家介紹了ForwardRef?useImperativeHandle方法demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
React父組件數(shù)據(jù)實(shí)時(shí)更新了,子組件沒(méi)有更新的問(wèn)題
這篇文章主要介紹了React父組件數(shù)據(jù)實(shí)時(shí)更新了,子組件沒(méi)有更新的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
react antd表格中渲染一張或多張圖片的實(shí)例
這篇文章主要介紹了react antd表格中渲染一張或多張圖片的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
React實(shí)現(xiàn)菜單欄滾動(dòng)功能
本文將會(huì)基于react實(shí)現(xiàn)滾動(dòng)菜單欄功能,點(diǎn)擊菜單,內(nèi)容區(qū)域會(huì)自動(dòng)滾動(dòng)到對(duì)應(yīng)卡片,內(nèi)容區(qū)域滑動(dòng),指定菜單欄會(huì)被選中,代碼簡(jiǎn)單易懂,感興趣的朋友一起看看吧2024-03-03
簡(jiǎn)單談?wù)凴eact中的路由系統(tǒng)
下面小編就為大家?guī)?lái)一篇簡(jiǎn)單談?wù)凴eact中的路由系統(tǒng)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07
從零開(kāi)始搭建webpack+react開(kāi)發(fā)環(huán)境的詳細(xì)步驟
這篇文章主要介紹了從零開(kāi)始搭建webpack+react開(kāi)發(fā)環(huán)境的詳細(xì)步驟,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05

