摘要:后兩個(gè)屬性可選。屬性定義了項(xiàng)目的縮小比例,默認(rèn)為,即如果空間不足,該項(xiàng)目將縮小。屬性定義了在分配多余空間之前,項(xiàng)目占據(jù)的主軸空間。它的默認(rèn)值為,即項(xiàng)目的本來(lái)大小。結(jié)合的異步組件和的代碼分割功能,輕松實(shí)現(xiàn)路由組件的懶加載。
項(xiàng)目總結(jié)
這是我第二個(gè)用 Vue 實(shí)現(xiàn)的項(xiàng)目,下面內(nèi)容包括了在實(shí)現(xiàn)過(guò)程中所記錄的知識(shí)點(diǎn)以及一些小技巧
項(xiàng)目演示地址:https://music-vue.n-y.io
源代碼地址:https://github.com/nanyang24/...
此應(yīng)用的全部數(shù)據(jù)來(lái)自 QQ音樂(lè),利用 axios 結(jié)合 node.js 代理后端請(qǐng)求抓取
全局通用的應(yīng)用級(jí)狀態(tài)使用 vuex 集中管理
全局引入 fastclick 庫(kù),消除 click 移動(dòng)瀏覽器 300ms 延遲
頁(yè)面是響應(yīng)式的,適配常見(jiàn)的移動(dòng)端屏幕,采用 flex 布局
疑難總結(jié) & 小技巧 關(guān)于 Vue 知識(shí) & 使用技巧 v-html 可以轉(zhuǎn)義字符,處理特定接口很有用 watch 對(duì)象可以觀測(cè) 屬性 的變化 像這種父組件傳達(dá)子組件的參數(shù)通常都是在data()里面定義的,為什么這里要放到created()定義,兩者有什么區(qū)別呢?因?yàn)檫@個(gè)變量不需要觀測(cè)它的變化,因此不用定義在 data 里,這樣也會(huì)對(duì)性能有所優(yōu)化
不明白什么時(shí)候要把變量放在data()里,什么時(shí)候又不需要放 ?需要監(jiān)測(cè)這個(gè)數(shù)據(jù)變化的時(shí)候,放在 data() 里,會(huì)給數(shù)據(jù)添加 getter 和 setter
生命周期 鉤子函數(shù)生命周期鉤子函數(shù),比如 mounted 是先觸發(fā)子組件的 mounted,再會(huì)觸發(fā)父組件的 mounted,但是對(duì)于 created 鉤子,又會(huì)先觸發(fā)父組件,再觸發(fā)子組件。
銷毀計(jì)數(shù)器如果組件有計(jì)數(shù)器,在組件銷毀時(shí)期要記得清理,好習(xí)慣
對(duì)于 Vue 組件,this.$refs.xxx 拿到的是 Vue 實(shí)例,所以需要再通過(guò) $el 拿到真實(shí)的 dom 關(guān)于 JS 知識(shí) & 技巧 setTimeout(fn, 20)一般來(lái)說(shuō) JS 線程執(zhí)行完畢后一個(gè) Tick 的時(shí)間約17ms內(nèi) DOM 就可以渲染完畢所以課程中 setTimeout(fn, 20) 是非常穩(wěn)妥的寫法
關(guān)于 webpack 知識(shí) & 技巧 " ~ " 使 SCSS 可以使用 webpack 的相對(duì)路徑@import "~common/scss/mixin"; @import "~common/scss/variable";babel-runtime 會(huì)在編譯階段把 es6 語(yǔ)法編譯的代碼打包到業(yè)務(wù)代碼中,所以要放在dependencies里。 Fast Click 是一個(gè)簡(jiǎn)單、易用的庫(kù),專為消除移動(dòng)端瀏覽器從物理觸摸到觸發(fā)點(diǎn)擊事件之間的300ms延時(shí) 為什么會(huì)存在延遲呢?
從觸摸按鈕到觸發(fā)點(diǎn)擊事件,移動(dòng)端瀏覽器會(huì)等待接近300ms,原因是瀏覽器會(huì)等待以確定你是否執(zhí)行雙擊事件
何時(shí)不需要使用FastClick 不會(huì)伴隨監(jiān)聽(tīng)任何桌面瀏覽器
Android 系統(tǒng)中,在頭部 meta 中設(shè)置 width=device-width 的Chrome32+ 瀏覽器不存在300ms 延時(shí),所以,也不需要
同樣的情況也適用于 Android設(shè)備(任何版本),在viewport 中設(shè)置 user-scalable=no,但這樣就禁止縮放網(wǎng)頁(yè)了
IE11+ 瀏覽器中,你可以使用 touch-action: manipulation; 禁止通過(guò)雙擊來(lái)放大一些元素(比如:鏈接和按鈕)。IE10可以使用 -ms-touch-action: manipulation
請(qǐng)求接口jsonp:
XHR:
手寫輪播圖利用 BScroll
BScroll 設(shè)置 loop 會(huì)自動(dòng) clone 兩個(gè)輪播插在前后位置
如果輪播循環(huán)播放,是前后各加一個(gè)輪播圖保證無(wú)縫切換,所以需要再加兩個(gè)寬度
if (this.loop) { width += 2 * sliderWidth }
初始化 dots 要在 BScroll 克隆插入兩個(gè)輪播圖之前
dots active狀態(tài) 是通過(guò)判斷 currentIndex 與 index 是否相等
currentIndex 更新是通過(guò)獲取 scroll 當(dāng)前 page,BScroll 提供了 api 方便調(diào)用
this.currentPageIndex = this.scroll.getCurrentPage().pageX
為了保證改變窗口大小依然正常輪播,監(jiān)聽(tīng)窗口 resize 事件,重新渲染輪播圖
window.addEventListener("resize", () => { if (!this.scroll || !this.scroll.enabled) return clearTimeout(this.resizeTimer) this.resizeTimer = setTimeout(() => { if (this.scroll.isInTransition) { this._onScrollEnd() } else { if (this.autoPlay) { this._play() } } this.refresh() }, 60) })
在切換 tab 相當(dāng)于 切換了 keep-alive 的組件
輪播會(huì)出問(wèn)題,需要手動(dòng)幫助執(zhí)行,利用了 activated , deactivated 鉤子函數(shù)
activated() { this.scroll.enable() let pageIndex = this.scroll.getCurrentPage().pageX this.scroll.goToPage(pageIndex, 0, 0) this.currentPageIndex = pageIndex if (this.autoPlay) { this._play() } }, deactivated() { this.scroll.disable() clearTimeout(this.timer) }
實(shí)測(cè),首次打開(kāi)網(wǎng)頁(yè)并不會(huì)執(zhí)行 activated,只有在之后切換 tab ,切回來(lái)才會(huì)執(zhí)行
在組件銷毀之前 beforeDestroy 銷毀定時(shí)器是好習(xí)慣,keep-alive 因?yàn)槭菍⒔M件緩存了,所以不會(huì)觸發(fā)
beforeDestroy() { this.scroll.disable() clearTimeout(this.timer) }后端接口代理
簡(jiǎn)單設(shè)置一下 Referer, Host,讓別人直接通過(guò)瀏覽器抓到你的接口
但是這種方式防不了后端代理的方式
前端 XHR 會(huì)有跨域限制,后端發(fā)送 http 請(qǐng)求則沒(méi)有限制,因此可以偽造請(qǐng)求
axios 可以在瀏覽器端發(fā)送 XMLHttpRequest 請(qǐng)求,在服務(wù)器端發(fā)送 http 請(qǐng)求
(在項(xiàng)目編寫階段,可以將后端代理請(qǐng)求寫在 webpack 的 dev 文件的 before 函數(shù)內(nèi))
before(app) { app.get("/api/getDiscList", function (req, res) { const url = "https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg" axios.get(url, { headers: { referer: "https://c.y.qq.com/", host: "c.y.qq.com" }, params: req.query }).then((response) => { res.json(response.data) // axios 返回的數(shù)據(jù)在 response.data,要把數(shù)據(jù)透?jìng)鞯轿覀冏远x的接口里面 res.json(response.data) }).catch((e) => { console.log(e) }) }); }
定義一個(gè)路由,get 到一個(gè) /api/getDiscList 接口,通過(guò) axios 偽造 headers,發(fā)送給QQ音樂(lè)服務(wù)器一個(gè) http 請(qǐng)求,還有 param 參數(shù)。
得到服務(wù)端正確的響應(yīng),通過(guò) res.json(response.data) 返回到瀏覽器端
另外 因?yàn)槭?http 請(qǐng)求數(shù)據(jù),是ajax,所以 format 參數(shù)要將原本接口的 jsonp 改為 json
大公司怎么防止被惡意代理呢?當(dāng)你的訪問(wèn)量大的時(shí)候,出口ip會(huì)被查到獲取封禁,還有一種就是參數(shù)驗(yàn)簽,也就是請(qǐng)求人家的數(shù)據(jù)必須帶一個(gè)簽名參數(shù),然后這個(gè)簽名參數(shù)是很難拿到的這個(gè)正確的簽名,從而達(dá)到保護(hù)數(shù)據(jù)的目的
當(dāng)然,獲取的數(shù)據(jù)并不能直接拿來(lái)用,需要做進(jìn)一步的規(guī)格化,達(dá)到我們使用的要求,所以在這方面多帶帶封裝了一個(gè) class 來(lái)處理這方面的數(shù)據(jù),具體請(qǐng)看src/common/js/song.js
flex 布局,熱門歌單推薦左側(cè) icon 固定大小,flex: 0 0 60px
flex 屬性是 flex-grow , flex-shrink 和 flex-basis 的簡(jiǎn)寫,默認(rèn)值為 0 1 auto。后兩個(gè)屬性可選。
flex-grow 屬性定義項(xiàng)目的放大比例,默認(rèn)為 0,即如果存在剩余空間,也不放大。
flex-shrink 屬性定義了項(xiàng)目的縮小比例,默認(rèn)為 1,即如果空間不足,該項(xiàng)目將縮小。
flex-basis 屬性定義了在分配多余空間之前,項(xiàng)目占據(jù)的主軸空間(main size)。瀏覽器根據(jù)這個(gè)屬性,計(jì)算主軸是否有多余空間。它的默認(rèn)值為auto,即項(xiàng)目的本來(lái)大小。
右側(cè) text 區(qū)塊 自適應(yīng)占據(jù)剩下的空間,并且內(nèi)部也采用 flex,使用 flex-direction: column; justify-content: center; 來(lái)達(dá)到縱向居中排列
recommend 頁(yè)面 利用 BScroll 滾動(dòng)Scroll 初始化但卻沒(méi)有滾動(dòng),是因?yàn)槌跏蓟瘯r(shí)機(jī)不對(duì),必須保證數(shù)據(jù)到來(lái),DOM 成功渲染之后 再去進(jìn)行初始化
可以使用父組件 給 Scrol組件傳 :data 數(shù)據(jù),Scroll 組件自己 watch 這個(gè) data,有變化就立刻 refesh 滾動(dòng)
新版本 BScroll 已經(jīng)自己實(shí)現(xiàn)檢測(cè) DOM 變化,自動(dòng)刷新,大部分場(chǎng)景下無(wú)需傳 data 了
所以也就 無(wú)需監(jiān)聽(tīng) img 的 onload 事件 然后執(zhí)行 滾動(dòng)刷新 了
迷你播放器暫停狀態(tài),進(jìn)入全屏,按鈕在進(jìn)度條最左邊
原因:當(dāng)播放器最小化的時(shí)候,progress-bar 仍然在監(jiān)聽(tīng) percent 的變化,所以在不斷計(jì)算進(jìn)度條的位置,然而這個(gè)時(shí)候由于播放器隱藏,進(jìn)度條的寬度 this.$refs.progressBar.clientWidth 計(jì)算為0,因此計(jì)算出來(lái)的 offset 也是不對(duì)的,導(dǎo)致再次最大化播放器的時(shí)候,由于播放器是暫停狀態(tài), percent 并不會(huì)變化,也不會(huì)重新計(jì)算這個(gè) offset ,導(dǎo)致 Bug。
解決方案:當(dāng)播放器最大化的時(shí)候,手動(dòng)去計(jì)算一次 offset,確保進(jìn)度條的位置正確。
progress-bar 組件要 watch 下 fullScreen,在進(jìn)入全屏的時(shí)候調(diào)用一下 移動(dòng)按鈕函數(shù)
歌詞 lyric獲取歌詞,雖然我們約定返回?cái)?shù)據(jù)是 json,但QQ音樂(lè) 返回的是依然是 jsonp,所以我們需要做一層數(shù)據(jù)的處理
const reg = /^w+(({.+}))$/
就是將返回的jsonp格式摘取出我們需要的json字段
ret = JSON.parse(matches[1])
將正則分組(就是正則括號(hào)內(nèi)的內(nèi)容)捕獲的json字符串?dāng)?shù)據(jù) 轉(zhuǎn)成 json 格式
然后我們?cè)?player 組件中監(jiān)聽(tīng) currentSong 的變化,獲取 this.currentSong.getLyric()
axios.get(url, { headers: { referer: "https://c.y.qq.com/", host: "c.y.qq.com" }, params: req.query }).then((response) => { let ret = response.data if (typeof ret === "string") { const reg = /^w+(({.+}))$/ const matches = ret.match(reg) if (matches) { ret = JSON.parse(matches[1]) } } res.json(ret) })
然后我們得到的返回?cái)?shù)據(jù)的是 base64 的字符串,需要解碼,這里用到了第三方庫(kù): js-base64
(我們這次用的是QQ音樂(lè)pc版的歌詞,需要解碼base64,而移動(dòng)版的QQ音樂(lè)是不需要的)
this.lyric = Base64.decode(res.lyric)
之后利用第三方庫(kù): js-lyric ,解析我們的歌詞,生成方便操作的對(duì)象
getLyric() { this.currentSong.getLyric() .then(lyric => { this.currentLyric = new Lyric(lyric) }) }歌詞滾動(dòng)
當(dāng)前歌曲的歌詞高亮是利用 js-lyric 會(huì)派發(fā)的 handle 事件
this.currentLyric = new Lyric(lyric, this.handleLyric)
js-lyric 會(huì)在每次改變當(dāng)前歌詞時(shí)觸發(fā)這個(gè)函數(shù),函數(shù)的參數(shù)為 當(dāng)前的 lineNum 和 txt
而 使當(dāng)前高亮歌詞保持最中間 是利用了 BScroll 滾動(dòng)至高亮的歌詞let middleLine = isIphoneX() ? 7 : 5 // 鑒于iphonex太長(zhǎng)了,做個(gè)小優(yōu)化 if (lineNum > middleLine) { let lineEl = this.$refs.lyricLine[lineNum - middleLine] this.$refs.lyricList.scrollToElement(lineEl, 1000) } else { this.$refs.lyricList.scrollTo(0, 0, 1000) }cd 與 歌詞 之間滑動(dòng)
通過(guò)監(jiān)聽(tīng) middle 的 三個(gè) touch 事件
offsetWidth 是為了計(jì)算歌詞列表的一個(gè)偏移量的,首先它的偏移量不能大于0,也不能小于 -window.innerWidth。
left 是根據(jù)當(dāng)前顯示的是 cd 還是歌詞列表初始化的位置,如果是 cd,那么 left 為 0 ,歌詞是從右往左拖的,deltaX 是小于 0 的,所以最終它的偏移量就是 0+deltaX;如果已經(jīng)顯示歌詞了,那么 left 為 -window.innerWidth,歌詞是從左往右拖,deltaX 是大于 0 的,所以最終它的偏移量就是 -window.innerWidth + deltaX。
middleTouchStart(e) { this.touch.initiated = true this.touch.startX = e.touches[0].pageX this.touch.startY = e.touches[0].pageY }, middleTouchMove(e) { if (!this.touch.initiated) return const deltaX = e.touches[0].pageX - this.touch.startX const deltaY = e.touches[0].pageY - this.touch.startY if (Math.abs(deltaY) > Math.abs(deltaX)) { return } const left = this.currentShow === "cd" ? 0 : -window.innerWidth const offsetWidth = Math.min(0, Math.max(-window.innerWidth, left + deltaX)) this.touch.percent = Math.abs(offsetWidth / window.innerWidth) console.log(this.touch.percent) this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px,0,0)` this.$refs.lyricList.$el.style[transitionDuration] = 0 this.$refs.middleL.style.opacity = 1 - this.touch.percent this.$refs.middleL.style[transitionDuration] = 0 }, middleTouchEnd() { let offsetWidth, opacity // 從右向左滑 的情況 if (this.currentShow === "cd") { if (this.touch.percent > 0.1) { offsetWidth = -window.innerWidth opacity = 0 this.currentShow = "lyric" } else { offsetWidth = 0 opacity = 1 } } else { // 從左向右滑 的情況 if (this.touch.percent < 0.9) { offsetWidth = 0 opacity = 1 this.currentShow = "cd" } else { offsetWidth = -window.innerWidth opacity = 0 } } const durationTime = 300 this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px,0,0)` this.$refs.lyricList.$el.style[transitionDuration] = `${durationTime}ms` this.$refs.middleL.style.opacity = opacity this.$refs.middleL.style[transitionDuration] = `${durationTime}ms` }優(yōu)化
Vue 按需加載路由:
當(dāng)打包構(gòu)建應(yīng)用時(shí),Javascript 包會(huì)變得非常大,影響頁(yè)面加載。如果我們能把不同路由對(duì)應(yīng)的組件分割成不同的代碼塊,然后當(dāng)路由被訪問(wèn)的時(shí)候才加載對(duì)應(yīng)組件,這樣就更加高效了。
結(jié)合 Vue 的異步組件和 Webpack 的代碼分割功能,輕松實(shí)現(xiàn)路由組件的懶加載。
首先,可以將異步組件定義為返回一個(gè) Promise 的工廠函數(shù) (該函數(shù)返回的 Promise 應(yīng)該 resolve 組件本身):
const Foo = () => Promise.resolve({ /* 組件定義對(duì)象 */ })
第二,在 Webpack 2 中,我們可以使用動(dòng)態(tài) import語(yǔ)法來(lái)定義代碼分塊點(diǎn) (split point):
import("./Foo.vue") // 返回 Promise
在我們的項(xiàng)目中的 router/index.js 是這樣定義的:
// Vue 異步加載路由 // 引入5個(gè) 一級(jí)路由組件 const Recommend = () => import("components/recommend/recommend") const Singer = () => import("components/singer/singer") const Rank = () => import("components/rank/rank") const Search = () => import("components/search/search") const UserCenter = () => import("components/user-center/user-center") // 二級(jí)路由組件 const SingerDetail = () => import("components/singer-detail/singer-detail") const Disc = () => import("components/disc/disc") const TopList = () => import("components/top-list/top-list")
無(wú)需改動(dòng)其他的代碼
手機(jī)聯(lián)調(diào)電腦,手機(jī) 同一WIFI下
配置 config 的 index.js 里的 host 為 "0.0.0.0",手機(jī)可以打開(kāi)電腦的IP地址+端口查看
mac下 ifconfig 查看ip
移動(dòng)端調(diào)試工具移動(dòng)端console:vConsole
移動(dòng)端抓包工具:charles
以上是在實(shí)現(xiàn)這個(gè)音樂(lè) Vue 項(xiàng)目中遇到的難點(diǎn)以及一些使用技巧。在這里記錄下來(lái)方便以后自己查閱,還能夠給同樣在前端這個(gè)小領(lǐng)域奮斗的大家提供一小些學(xué)習(xí)資料~
我的 Github:https://github.com/nanyang24
如果對(duì)你有幫助,歡迎 star 和 互粉 ~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/107496.html
摘要:前言最近在自學(xué)打算自己仿一個(gè)項(xiàng)目來(lái)實(shí)戰(zhàn)一下,由于本人很喜歡聽(tīng)歌,所以就選擇了網(wǎng)易云音樂(lè),在這與大家分享一下自己所遇到的問(wèn)題,其中也有些不足之處也希望大家提一些寶貴的意見(jiàn),互相學(xué)習(xí),一起進(jìn)步。 showImg(https://segmentfault.com/img/remote/1460000015805758); 前言 最近在自學(xué)vue,打算自己仿一個(gè)項(xiàng)目來(lái)實(shí)戰(zhàn)一下,由于本人很喜歡聽(tīng)...
前言:當(dāng)下音樂(lè)播放器不勝其數(shù),為了更好的掌握一些東西,我們來(lái)自己制作一個(gè)音樂(lè)播放器。 文章目錄: 一.開(kāi)發(fā)環(huán)境:二.頁(yè)面視圖:1.主文件入口(首頁(yè)):2.音樂(lè)播放界面: 三.功能實(shí)現(xiàn)(1)、index.html:(2)、播放音樂(lè)(music.html):(3)、樣式文件(index.css): 四.項(xiàng)目地址: 一.開(kāi)發(fā)環(huán)境: 開(kāi)發(fā)工具:HbuliderX; 框架:Vant,Mui,V...
摘要:在中新建組件許文瑞正在吃屎。。。。在中添加如下代碼三歌手組件開(kāi)發(fā)歌手首頁(yè)開(kāi)發(fā)數(shù)據(jù)獲取數(shù)據(jù)獲取依舊從音樂(lè)官網(wǎng)獲取歌手接口創(chuàng)建我們和以前一樣,利用我們封裝的等發(fā)放,來(lái)請(qǐng)求我們的接口,返回給。 Vue-Music 跟學(xué)一個(gè)網(wǎng)課老師做的仿原生音樂(lè)APP跟學(xué)的筆記,記錄點(diǎn)滴,也希望對(duì)學(xué)習(xí)vue初學(xué)小伙伴有點(diǎn)幫助 showImg(https://segmentfault.com/img/remot...
摘要:每次用網(wǎng)易云音樂(lè)客戶端播放聽(tīng)歌的時(shí)候,收藏的歌曲,在我的博客上也可以同步進(jìn)行更新。 最近應(yīng)該發(fā)現(xiàn),我的博客https://blog.codelabo.cn左下角多了一個(gè)音樂(lè)播放器 showImg(https://segmentfault.com/img/remote/1460000016786096?w=1806&h=952); 這個(gè)是怎么實(shí)現(xiàn)的?一起來(lái)看看吧 APlayer 首先我們...
閱讀 2674·2023-04-25 15:15
閱讀 1316·2021-11-25 09:43
閱讀 1603·2021-11-23 09:51
閱讀 1078·2021-11-12 10:36
閱讀 2880·2021-11-11 16:55
閱讀 954·2021-11-08 13:18
閱讀 722·2021-10-28 09:31
閱讀 2047·2019-08-30 15:47