關(guān)于實現(xiàn)Vue3版抖音滑動插件踩坑指南
起步
年前單位需要搞一個類似抖音的需求,這本應(yīng)是客戶端的任務(wù),然而,不知天高地厚的我卻接了下來,然而下細致調(diào)研之下,發(fā)現(xiàn)網(wǎng)上并沒有成熟的方案,但是卻又很多需求,各大論壇全是提問的帖子,卻少有人回答和解決。
這一瞬間,俺慌了,畢竟單位的活,排期都是定死的,這時候臨陣退縮,實乃下下策。于是只能擼起袖子加油干。畢竟自己攬的事,含著淚也要干完,這就是男人,一個吐沫一個釘!
調(diào)研
大家知道,web端比起客戶端的劣勢有幾點,想要做出類似客戶端的復(fù)雜的交互效果,需要考慮幾個問題:
- 1、性能--怎樣能達到最優(yōu)(當(dāng)然想要跟客戶端比肩這是不可能的)
- 2、體驗--怎樣達到客戶端的優(yōu)秀體驗,比如視頻緩沖怎么處理,初始化怎么處理,要不要視頻預(yù)加載
- 3、兼容--ios 和安卓的設(shè)備以及他們各個版本之間的瀏覽器的實現(xiàn)都略有不同
而在我調(diào)研了抖音的web端、git上的一些開源的相關(guān)項目、以及一些零零散散的回答之后,發(fā)現(xiàn)都不太匹配 他們在實現(xiàn)上,那么只能集幾百家之長自己來了,既然自己來就需要針對當(dāng)前三個問題來尋找既能解決問題,又能快速實現(xiàn)的方案(畢竟有排期)
實現(xiàn)思路
在實現(xiàn)的初步設(shè)想中,我們不只需要解決問題,其實也需要考慮一些架構(gòu)設(shè)計,也就是你怎樣去將關(guān)注度分離,怎樣將組件的顆粒度拆的細致,能將每一個組件獨立出來,外部單獨引用,怎樣將每一個組件做通用,方便日后維護,并且還能快速開發(fā),不耽誤排期,這其實就是你在這做也無需求之初需要去想的一些問題,總結(jié)如下
- 首先來說毋庸置疑的是:要想實現(xiàn)滑動的效果,現(xiàn)成的方案最快的就是swiper,swiper在web端的地位也是不可動搖。
- 其次原生video標(biāo)簽體驗大家也都知道,一塌糊涂,那么這一塊也就需要自己實現(xiàn),比如進度條,拖動,暫停播放,緩沖中等等內(nèi)容。并且類似抖音中的視頻上方的一些元素,比如點贊,分享等功能需要外部傳入,讓別的開發(fā)者在使用時自己定制
- 怎樣將組件的的結(jié)構(gòu)拆分出來,能單獨打包上傳npm 供大家使用
組件設(shè)計的設(shè)想俺才疏學(xué)淺也就能想到這了,接下來就該解決在調(diào)研中發(fā)現(xiàn)的三個問題:
- 最容易解決的問題就是兼容問題,babel完美解決,cli工具命令行直接生成,swiper 在能實現(xiàn)功能的情況下盡量使用老的版本
- 性能問題是最難解決,如果渲染到很多視頻之后,難免會有很多的video存在于dom中,這里我們采用了web抖音的方案,在整個dom中只渲染一個活動sidle的video其他的slide中渲染空節(jié)點,這樣就能大大減少dom的數(shù)量,再配合vue的diff 能提供一個還算過得去的性能
- 體驗問題雖然不難,但是僅僅靠前端是無法解決的,需要多方配合,他需要壓縮視頻大小,提供封面圖,增加緩沖效果,等等,而且不同的設(shè)備不同系統(tǒng)不同版本在video的表現(xiàn)差異還非常大,這其實是一個不可用技術(shù)解決的兼容問題,那么,我們只能從交互上來解決問題。
工程構(gòu)建
工程構(gòu)建為了裝逼上了最新的vite ,體驗了一把,開發(fā)體驗確實是絲滑快速。由于vite天生支持庫的開發(fā),只需要在vite.config.ts 添加build內(nèi)容即可
build: { lib: { entry: path.resolve(__dirname, 'src/components/index.ts'), name: 'videoSlide', fileName: (format) => `index.${format}.js` }, rollupOptions: { // 確保外部化處理那些你不想打包進庫的依賴 external: ['vue'], output: { // 在 UMD 構(gòu)建模式下為這些外部化的依賴提供一個全局變量 globals: { vue: 'Vue' } } } },
由于庫可能給ts大佬使用,需要安裝vite-plugin-dts 插件,來生成d.ts文件
代碼實現(xiàn)
由于視頻內(nèi)容和輪播部分的處理是兩個獨立的邏輯,所以將代碼拆分為兩個組件video.vue以及slide.vue
video實現(xiàn)
video的實現(xiàn)的基本思路就是重寫原生video 標(biāo)簽?zāi)Jui來達到自定義的目的,樣式就不在贅述,主要就是video提供的一些事件重寫video默認行為,這里簡述下重點的函數(shù)
// vue <video playsinline="true" webkit-playsinline="true" mediatype="video" :poster="poster" @progress="progress" @durationchange="durationchange" @loadeddata="loadeddata" @playing="playing" @waiting="waiting" @timeupdate="timeupdate" @canplay="playing" @ended="ended" > <source :src="src" type="video/mp4" /> </video> //js setup({ autoplay }) { // 是否是暫停狀態(tài) const paused = ref(true); // 視頻總時間 const endTime = ref(second(0)); //播放的時間 const startTime = ref(second(0)); // 是否是按下狀態(tài) const isPress = ref(false); //緩沖進度 const percentageBuffer = ref(0); // 播放進度 const percentage = ref(0); // 保存計算后的播放時間 const calculationTime = ref(0); // 拿到video 實例 const video = ref(null); // 是否展示封面圖 const showImg = ref(true); // 是否處于緩沖中 const loading = ref(false); // 播放 function play() { video.value.play(); paused.value = false; } // 暫停 function pause() { if (paused.value) return; video.value.pause(); paused.value = true; loading.value = false; } // 獲取緩沖進度 function progress() { if (!video.value) return; percentageBuffer.value = Math.floor( (video.value.buffered.length ? video.value.buffered.end(video.value.buffered.length - 1) / video.value.duration : 0) * 100 ); } // 時間改變 function durationchange() { endTime.value = second(video.value.duration); console.log("時間改變觸發(fā)"); } // 首幀加載觸發(fā),為了獲取視頻時長 function loadeddata() { console.log("首幀渲染觸發(fā)"); showImg.value = false; autoplay && play(); } //當(dāng)播放準(zhǔn)備開始時(之前被暫停或者由于數(shù)據(jù)缺乏被暫緩)被觸發(fā) function playing() { console.log("緩沖結(jié)束"); loading.value = false; } //緩沖的時候觸發(fā) function waiting() { console.log("處于緩沖中"); loading.value = true; } // 時間改變觸發(fā) function timeupdate() { // 如果是按下狀態(tài)不能走進度,表示需要執(zhí)行拖動 if (isPress.value || !video.value) return; startTime.value = second(Math.floor(video.value.currentTime)); percentage.value = Math.floor( (video.value.currentTime / video.value.duration) * 100 ); } // 按下開始觸發(fā) function touchstart() { isPress.value = true; } //松開按鈕觸發(fā) function touchend() { isPress.value = false; video.value.currentTime = calculationTime.value; } // 拖動的時候觸發(fā) function touchmove(e) { const width = window.screen.width; const tx = e.clientX || e.changedTouches[0].clientX; if (tx < 0 || tx > width) { return; } calculationTime.value = video.value.duration * (tx / width); startTime.value = second(Math.floor(calculationTime.value)); percentage.value = Math.floor((tx / width) * 100); } //點擊進度條觸發(fā) function handleProgress(e) { touchmove(e); touchend(); } // 播放結(jié)束時觸發(fā) function ended() { play(); } onMounted(() => {}); return { video, paused, pause, play, progress, durationchange, loadeddata, endTime, startTime, playing, percentage, waiting, timeupdate, percentageBuffer, touchstart, touchend, touchmove, isPress, ended, handleProgress, loading, showImg, }; },
需要注意的是,需要自定義內(nèi)容交給了使用者去自定義,全部通過插槽傳入當(dāng)前組件,這樣就方便了根據(jù)內(nèi)容自定義樣式了
slide.vue
slide.vue 就是處理滑動內(nèi)容的組件,他包含了常用的上拉刷新,預(yù)加載等內(nèi)容核心代碼如下:
// vue <swiper direction="vertical" @transitionStart="transitionStart" > <swiper-slide class="slide-box" v-for="(item, index) in list" :key="index"> <slot :item="item" :index="index" :activeIndex="activeIndex" v-if="activeIndex >= index - 1 && activeIndex <= index + 1" ></slot> </swiper-slide> </swiper> //js setup({ list }, { emit }) { const activeIndex = ref(0); function transitionStart(swiper) { //表示沒有滑動,不做處理 if (activeIndex.value === swiper.activeIndex) { // 表示是第一個輪播圖 if (swiper.swipeDirection === "prev" && swiper.activeIndex === 0) { // 表示上拉刷新 emit("refresh"); } else if ( swiper.swipeDirection === "next" && swiper.activeIndex === list.length - 1 ) { // 滑動到底部 emit("toBottom"); } } else { activeIndex.value = swiper.activeIndex; // 為了預(yù)加載視頻,提前l(fā)oad 數(shù)據(jù) if (swiper.activeIndex === list.length - 1) { emit("load"); } } } return { transitionStart, activeIndex, }; },
需要注意的是有兩點
為了預(yù)加載數(shù)據(jù)會在滑動到最后一幀的時候去請求數(shù)據(jù),但是由于請求是異步的,如果在滑動到最后一個視頻的時候在快速下滑會觸發(fā)滑動到底部的事件,這時候其實新數(shù)據(jù)請求回來之后便又不是底部了,這時候則需要你去做個判斷,如果正在請求中滑動到底部不去處理你的邏輯
為了性能考慮,只渲染了active 、prev、next內(nèi)容,其他一律渲染空節(jié)點,并且為了防止頁面中出現(xiàn)多個vidoe標(biāo)簽,prev 和next 只渲染默認圖內(nèi)容
組合使用
組合使用其實就非常簡單了:
//vue <Yslide :list="data" v-slot="{ item, index, activeIndex }" @refresh="refresh" @toBottom="toBottom" @load="load" > <Yvideo :src="item.entStoreVO.video" :poster="item.entStoreVO.videoImg" :index="index" :activeIndex="activeIndex" autoplay > <div class="mantle"> <div class="right" @click.stop=""> <div class="right-btn fabulous" @click="fabulous">點贊</div> <div class="right-btn comment" @click="comment">評論</div> <div class="right-btn collection" @click="collection">收藏</div> <div class="right-btn share" @click="share">分享</div> </div> </div> </Yvideo> </Yslide>
在組合使用中,我將video通過插槽的方式傳入silide內(nèi)部,這樣做的原因是,為了用戶能自定義傳入內(nèi)容,這也是很多插件庫慣用的伎倆,增加了組件的靈活性,又增加了組件的獨立性
視頻自動播放問題
在web瀏覽器中你經(jīng)常會看到DOMException: play() failed because the user didn't interact with the document first 這個問題,
首先可以肯定的是在web瀏覽器中在與瀏覽器沒有交互的情況下是不允許自動播放的,目前暫時還無法突破這個限制
如果你要嵌入app中,webview 可以突破,具體方法大家可自行查詢,網(wǎng)上教程數(shù)不勝數(shù)。
git地址
將插件地址奉上,供大佬們參考,如有需求可直接引用,也可,克隆下來自行修改,如有問題請?zhí)醝ssues github.com/yixinagqing…
總結(jié)
到此這篇關(guān)于實現(xiàn)Vue3版抖音滑動插件踩坑指南的文章就介紹到這了,更多相關(guān)Vue3版抖音滑動插件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue使用echarts實現(xiàn)動態(tài)數(shù)據(jù)的示例詳解
這篇文章主要為大家詳細介紹了vue如何使用echarts實現(xiàn)動態(tài)數(shù)據(jù),文中的示例講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11vue使用keep-alive后從部分頁面進入不緩存示例詳解
這篇文章主要給大家介紹了關(guān)于vue使用keep-alive后從部分頁面進入不緩存的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-03-03vue3+element 分片上傳與分片下載功能實現(xiàn)方法詳解
這篇文章主要介紹了vue3+element 分片上傳與分片下載功能實現(xiàn)方法,結(jié)合實例形式詳細分析了vue3+element 分片上傳與下載相關(guān)實現(xiàn)技巧與操作注意事項,需要的朋友可以參考下2023-06-06詳解vuex持久化插件解決瀏覽器刷新數(shù)據(jù)消失問題
這篇文章主要介紹了詳解vuex持久化插件解決瀏覽器刷新數(shù)據(jù)消失問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-04-04Vue中的無限加載vue-infinite-loading的方法
本篇文章主要介紹了Vue中的無限加載vue-infinite-loading的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04Vue3?攜手?TypeScript?搭建完整項目結(jié)構(gòu)
TypeScript 是JS的一個超級,主要提供了類型系統(tǒng)和對ES6的支持,使用 TypeScript 可以增加代碼的可讀性和可維護性,在 react 和 vue 社區(qū)中也越來越多人開始使用TypeScript,這篇文章主要介紹了Vue3?攜手?TypeScript?搭建完整項目結(jié)構(gòu),需要的朋友可以參考下2022-04-04vue實現(xiàn)手機號碼的校驗實例代碼(防抖函數(shù)的應(yīng)用場景)
這篇文章主要給大家介紹了關(guān)于vue實現(xiàn)手機號碼的校驗的相關(guān)資料,主要是防抖函數(shù)的應(yīng)用場景,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09