日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
實(shí)現(xiàn)一個Vue3版抖音滑動插件踩坑指南!

起步

年前單位需要搞一個類似抖音的需求,這本應(yīng)是客戶端的任務(wù),然而,不知天高地厚的我卻接了下來,然而下細(xì)致調(diào)研之下,發(fā)現(xiàn)網(wǎng)上并沒有成熟的方案,但是卻又很多需求,各大論壇全是提問的帖子,卻少有人回答和解決。

這一瞬間,俺慌了,畢竟單位的活,排期都是定死的,這時候臨陣退縮,實(shí)乃下下策。于是只能擼起袖子加油干。畢竟自己攬的事,含著淚也要干完,這就是男人,一個吐沫一個釘!

調(diào)研

大家知道,web端比起客戶端的劣勢有幾點(diǎn),想要做出類似客戶端的復(fù)雜的交互效果,需要考慮幾個問題:

  • 性能--怎樣能達(dá)到最優(yōu)(當(dāng)然想要跟客戶端比肩這是不可能的)
  • 體驗(yàn)--怎樣達(dá)到客戶端的優(yōu)秀體驗(yàn),比如視頻緩沖怎么處理,初始化怎么處理,要不要視頻預(yù)加載
  • 兼容--ios 和安卓的設(shè)備以及他們各個版本之間的瀏覽器的實(shí)現(xiàn)都略有不同

而在我調(diào)研了抖音的web端、git上的一些開源的相關(guān)項(xiàng)目、以及一些零零散散的回答之后,發(fā)現(xiàn)都不太匹配 他們在實(shí)現(xiàn)上,那么只能集幾百家之長自己來了,既然自己來就需要針對當(dāng)前三個問題來尋找既能解決問題,又能快速實(shí)現(xiàn)的方案(畢竟有排期)

實(shí)現(xiàn)思路

在實(shí)現(xiàn)的初步設(shè)想中,我們不只需要解決問題,其實(shí)也需要考慮一些架構(gòu)設(shè)計,也就是你怎樣去將關(guān)注度分離,怎樣將組件的顆粒度拆的細(xì)致,能將每一個組件獨(dú)立出來,外部單獨(dú)引用,怎樣將每一個組件做通用,方便日后維護(hù),并且還能快速開發(fā),不耽誤排期,這其實(shí)就是你在這做也無需求之初需要去想的一些問題,總結(jié)如下

首先,來說毋庸置疑的是:要想實(shí)現(xiàn)滑動的效果,現(xiàn)成的方案最快的就是swiper,swiper在web端的地位也是不可動搖。

其次,原生video標(biāo)簽體驗(yàn)大家也都知道,一塌糊涂,那么這一塊也就需要自己實(shí)現(xiàn),比如進(jìn)度條,拖動,暫停播放,緩沖中等等內(nèi)容。并且類似抖音中的視頻上方的一些元素,比如點(diǎn)贊,分享等功能需要外部傳入,讓別的開發(fā)者在使用時自己定制

最后,怎樣將組件的的結(jié)構(gòu)拆分出來,能單獨(dú)打包上傳npm 供大家使用。

組件設(shè)計的設(shè)想俺才疏學(xué)淺也就能想到這了,接下來就該解決在調(diào)研中發(fā)現(xiàn)的三個問題:

  • 最容易解決的問題就是兼容問題,babel完美解決,cli工具命令行直接生成,swiper 在能實(shí)現(xiàn)功能的情況下盡量使用老的版本。
  • 性能問題是最難解決,如果渲染到很多視頻之后,難免會有很多的video存在于dom中,這里我們采用了web抖音的方案,在整個dom中只渲染一個活動sidle的video其他的slide中渲染空節(jié)點(diǎn),這樣就能大大減少dom的數(shù)量,再配合vue的diff 能提供一個還算過得去的性能。
  • 體驗(yàn)問題雖然不難,但是僅僅靠前端是無法解決的,需要多方配合,他需要壓縮視頻大小,提供封面圖,增加緩沖效果,等等,而且不同的設(shè)備不同系統(tǒng)不同版本在video的表現(xiàn)差異還非常大,這其實(shí)是一個不可用技術(shù)解決的兼容問題,那么,我們只能從交互上來解決問題。

工程構(gòu)建

工程構(gòu)建為了裝逼上了最新的vite ,體驗(yàn)了一把,開發(fā)體驗(yàn)確實(shí)是絲滑快速。由于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: {
// 確保外部化處理那些你不想打包進(jìn)庫的依賴
external: ['vue'],
output: {
// 在 UMD 構(gòu)建模式下為這些外部化的依賴提供一個全局變量
globals: {
vue: 'Vue'
}
}
}
},

由于庫可能給ts大佬使用,需要安裝vite-plugin-dts 插件,來生成d.ts文件。

代碼實(shí)現(xiàn)

由于視頻內(nèi)容和輪播部分的處理是兩個獨(dú)立的邏輯,所以將代碼拆分為兩個組件video.vue以及slide.vue。

video實(shí)現(xiàn)

video的實(shí)現(xiàn)的基本思路就是重寫原生video 標(biāo)簽?zāi)J(rèn)ui來達(dá)到自定義的目的,樣式就不在贅述,主要就是video提供的一些事件重寫video默認(rèn)行為,這里簡述下重點(diǎn)的函數(shù)。

// vue
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"
>


//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);
//緩沖進(jìn)度
const percentageBuffer = ref(0);
// 播放進(jìn)度
const percentage = ref(0);
// 保存計算后的播放時間
const calculationTime = ref(0);
// 拿到video 實(shí)例
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;
}
// 獲取緩沖進(jìn)度
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)不能走進(jìn)度,表示需要執(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);
}
//點(diǎn)擊進(jìn)度條觸發(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
direction="vertical"
@transitionStart="transitionStart"
>

:item="item"
:index="index"
:activeIndex="activeIndex"
v-if="activeIndex >= index - 1 && activeIndex <= index + 1"
>


//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,
};
},

需要注意的是有兩點(diǎn):

  • 為了預(yù)加載數(shù)據(jù)會在滑動到最后一幀的時候去請求數(shù)據(jù),但是由于請求是異步的,如果在滑動到最后一個視頻的時候在快速下滑會觸發(fā)滑動到底部的事件,這時候其實(shí)新數(shù)據(jù)請求回來之后便又不是底部了,這時候則需要你去做個判斷,如果正在請求中滑動到底部不去處理你的邏輯。
  • 為了性能考慮,只渲染了active 、prev、next內(nèi)容,其他一律渲染空節(jié)點(diǎn),并且為了防止頁面中出現(xiàn)多個vidoe標(biāo)簽,prev 和next 只渲染默認(rèn)圖內(nèi)容。

組合使用

組合使用其實(shí)就非常簡單了。

//vue
:list="data"
v-slot="{ item, index, activeIndex }"
@refresh="refresh"
@toBottom="toBottom"
@load="load"
>
:src="item.entStoreVO.video"
:poster="item.entStoreVO.videoImg"
:index="index"
:activeIndex="activeIndex"
autoplay
>


點(diǎn)贊

評論

收藏







在組合使用中,我將video通過插槽的方式傳入silide內(nèi)部,這樣做的原因是,為了用戶能自定義傳入內(nèi)容,這也是很多插件庫慣用的伎倆,增加了組件的靈活性,又增加了組件的獨(dú)立性。

視頻自動播放問題

在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í)醝ssuesgithub.com/yixinagqing…


當(dāng)前標(biāo)題:實(shí)現(xiàn)一個Vue3版抖音滑動插件踩坑指南!
文章鏈接:http://www.dlmjj.cn/article/cciehho.html