Vue實(shí)現(xiàn)無(wú)限輪播效果時(shí)動(dòng)態(tài)綁定style失效的解決方法
前言
最近在開發(fā)中遇到了一個(gè)新需求:列表輪播滾動(dòng);這個(gè)需求其實(shí)是比較常見(jiàn)的,實(shí)現(xiàn)方式也有很多,比如使用第三方插件:vue-seamless-scroll
,但是由于不想依賴第三方插件,想自己實(shí)現(xiàn),于是我開始了嘗試,但是在這個(gè)過(guò)程中遇到了動(dòng)態(tài)綁定style樣式不生效、綁定值與渲染值不一致等問(wèn)題。謹(jǐn)以此篇記錄自己的學(xué)習(xí)過(guò)程,也希望可以幫助到其他同學(xué)。
基礎(chǔ)代碼如下
創(chuàng)建一個(gè) scroll.vue
組件,內(nèi)容如下:
- html
<div class="domScroll"> <div :class="['scroll-content', rollClass]"> <slot></slot> <slot></slot> </div> </div>
- js
export default { data() { return { rollClass: "" }; } }
- css樣式
<style lang="less" scoped> .domScroll { overflow: hidden; } </style>
實(shí)現(xiàn)方式1(不推薦)
tableScroll() { this.$nextTick(() => { const tabDom = this.$el.querySelector(".scroll-content"); const rowOffsetHeight = this.$el.querySelector(".row-item") .offsetHeight;; setInterval(() => { const shift = this.data[0]; setTimeout(() => { this.data.push(shift); tabDom.style.transition = "all .5s"; tabDom.style.marginTop = `-${rowOffsetHeight}px`; }, 500); setTimeout(() => { this.data.splice(0, 1); tabDom.style.transition = "all 0s ease 0s"; tabDom.style.marginTop = "0"; }, 1000); }, 1500); }); }
代碼分析
rowOffsetHeight
:獲取列表 item 高度,這通常是單行的高度,用于后續(xù)計(jì)算滾動(dòng)的位移。獲取
data
數(shù)組中的第一個(gè)元素,這個(gè)元素將在滾動(dòng)時(shí)移動(dòng)到列表的底部第一個(gè)
setTimeout
延遲 500 毫秒后執(zhí)行以下操作:
this.data.push(shift)
:將第一個(gè)元素添加到data
數(shù)組的末尾,模擬循環(huán)移動(dòng)。tabDom.style.transition = "all .5s"
:設(shè)置過(guò)渡效果為 0.5 秒,表示 marginTop 的變化將有動(dòng)畫效果。tabDom.style.marginTop =
-${rowOffsetHeight}px`` :將tabDom
的上邊距設(shè)置為負(fù)的行高度,造成看似向上滾動(dòng)的效果。
第二個(gè)
setTimeout
延遲 1000 毫秒后執(zhí)行以下操作:this.data.splice(0, 1)
:移除data
數(shù)組中的第一個(gè)元素,完成滾動(dòng)。tabDom.style.transition = "all 0s ease 0s"
:取消過(guò)渡效果,確保下次滾動(dòng)時(shí)沒(méi)有延遲。tabDom.style.marginTop = "0"
:將 marginTop 重置為 0,以準(zhǔn)備下一次滾動(dòng)。
主要是通過(guò)不停的將第一條數(shù)據(jù)添加到數(shù)組末尾、然后刪除當(dāng)前第一條數(shù)據(jù),同時(shí)添加刪除動(dòng)畫樣式來(lái)實(shí)現(xiàn)滾動(dòng)效果。這種方式添加刪除時(shí)會(huì)出現(xiàn)停頓卡頓的現(xiàn)象。
實(shí)現(xiàn)方式2(推薦)
初始化方法
init() { const scrollContent = this.$el.querySelector(".scroll-content"); if (scrollContent) { const offsetHeight = scrollContent.offsetHeight; const scrollClass = this.setScrollClass(offsetHeight / 2, this.speed); this.rollClass = scrollClass; } },
首先通過(guò) this.$el.querySelector(".scroll-content");
獲取到目標(biāo)dom元素,如果該dom存在,獲取其 offsetHeight
值。
然后調(diào)用 setScrollClass
方法傳參:
- offsetHeight / 2是dom的一半高度
- speed 輪播速度
最后將返回值設(shè)置給全局變量 rollClass
setScrollClass方法
setScrollClass(offsetHeight, speed) { const uid = Math.random().toString(36).substring(2, 5); const style = document.createElement("style"); style.innerHTML = ` @keyframes listRowup${uid} { 0% { -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } 100% { -webkit-transform: translate3d(0, -50%, 0); transform: translate3d(0, -50%, 0); } } .rowup-${uid} { -webkit-animation: ${Math.floor( (offsetHeight * 1000) / speed )}ms listRowup${uid} linear infinite normal; animation: ${Math.floor( (offsetHeight * 1000) / speed )}ms listRowup${uid} linear infinite normal; } `; document.getElementsByTagName("head")[0].appendChild(style); return `rowup-${uid}`; }
該函數(shù)主要職責(zé):
- 使用
document.createElement("style")
方法創(chuàng)建一個(gè)style標(biāo)簽并插入@keyframes
動(dòng)畫、class
- 最終獲取
head
標(biāo)簽將生成的style
標(biāo)簽插入 - 返回生成的好的動(dòng)畫
class類名
組件使用
在主文件引入 scroll.vue
- html
<Domscroll :height="140"> <div class="listBox flex"> <div v-for="item in scrollList" :key="item" class="item" > {{ item }} </div> </div> </Domscroll>
- js
computed: { scrollList() { return [...this.list1,...this.list1] }, data() { return { list1: [ "機(jī)構(gòu)類", "區(qū)域類", "國(guó)家類", "證照文書類", "業(yè)務(wù)對(duì)象類", "人員類", "法律法規(guī)類型", "金融類" ] } }
需要注意的是這里需要將原數(shù)據(jù)拷貝成兩份數(shù)據(jù),我這里使用計(jì)算屬性+ 擴(kuò)展運(yùn)算符實(shí)現(xiàn) scrollList
方法返回雙份數(shù)據(jù)。
開始優(yōu)化
在上面方式1中的代碼已經(jīng)可以實(shí)現(xiàn)dom列表的無(wú)限滾動(dòng)效果;但是可以看出來(lái)該代碼有優(yōu)化的空間。
- 動(dòng)態(tài)插入css動(dòng)畫
- 動(dòng)態(tài)創(chuàng)建標(biāo)簽插入style
修改基礎(chǔ)代碼
添加: ref="scrollContent"
- html
<div class="domScroll"> <div ref="scrollContent" :class="['scroll-content']" :style="scrollSty"> <slot></slot> <slot></slot> </div> </div>
添加:scrollSty變量
- js
export default { data() { return { scrollSty: { animation: "" } }; } }
將css動(dòng)畫添加到css中
- css樣式
<style lang="less" scoped> .domScroll { overflow: hidden; @keyframes rowup { 0% { -webkit-transform: translate(0, 0); transform: translate(0, 0); } 100% { -webkit-transform: translate(0, -50%); transform: translate(0, -50%); } } } </style>
修改methods方法
dynamicStyle(speed) { const scrollContent = this.$refs.scrollContent; const offsetHeight = scrollContent.offsetHeight / 2; const duration = Math.floor((offsetHeight * 1000) / speed); this.scrollSty = { animation: `${duration}ms listRowup linear infinite normal`, "-webkit-animation": `${duration}ms listRowup linear infinite normal` }; },
代碼分析
獲取dom
通過(guò)this.$refs.scrollContent
直接獲取組件的 DOM 元素計(jì)算偏移高度
offsetHeight
:獲取scrollContent
元素的高度,并將其除以 2,計(jì)算出需要滾動(dòng)的高度。這里因?yàn)閯?dòng)畫只需要滾動(dòng)一半的內(nèi)容,因?yàn)槲覀兊臄?shù)據(jù)復(fù)制了兩份。計(jì)算動(dòng)畫時(shí)間
duration
: 根據(jù)offsetHeight
和speed
計(jì)算動(dòng)畫的持續(xù)時(shí)間,公式為:offsetHeight * 1000
:將高度轉(zhuǎn)換為毫秒(假設(shè)speed
是以像素/秒為單位)Math.floor
:向下取整,確保持續(xù)時(shí)間是一個(gè)整數(shù)。
動(dòng)畫參數(shù)
animation
設(shè)置 CSS 動(dòng)畫的屬性:
${duration}ms
:指定動(dòng)畫持續(xù)時(shí)間(毫秒)。listRowup
:應(yīng)為 CSS 中定義的動(dòng)畫名稱,表示動(dòng)畫的具體效果。linear
:表示動(dòng)畫的速度曲線是線性的。infinite
:表示動(dòng)畫將無(wú)限循環(huán)。normal
:表示動(dòng)畫的播放方向?yàn)檎7较颍◤拈_始到結(jié)束)。
遇到問(wèn)題
當(dāng)在 mounted
中執(zhí)行 dynamicStyle
方法時(shí)發(fā)現(xiàn)動(dòng)畫沒(méi)有生效,檢查元素后發(fā)現(xiàn)確實(shí)成功綁定動(dòng)畫了,但是為什么不生效呢?
于是將結(jié)果值與dom元素打印出來(lái)做對(duì)比:
發(fā)現(xiàn)動(dòng)態(tài)綁定的style
animation
的屬性結(jié)果與預(yù)期(函數(shù)設(shè)置的style)綁定的值竟然不同! 我一臉懵逼,反復(fù)查看代碼發(fā)現(xiàn)沒(méi)有錯(cuò)誤??!
嘗試解決
mounted
中使用$nextTick(function(){})
方法無(wú)效修改@keyframes動(dòng)畫名稱無(wú)效
最終解決
- 動(dòng)畫css單獨(dú)寫一個(gè)style標(biāo)簽,不添加scoped
<style lang="less" scoped> .domScroll { overflow: hidden; } </style> <style> @keyframes rowup { 0% { -webkit-transform: translate(0, 0); transform: translate(0, 0); } 100% { -webkit-transform: translate(0, -50%); transform: translate(0, -50%); } } </style>
經(jīng)過(guò)查資料分析發(fā)現(xiàn)原來(lái)是因?yàn)?style
標(biāo)簽的 scoped
屬性導(dǎo)致的動(dòng)畫樣式的選擇器不匹配
在 scoped
樣式中生成的類名會(huì)自動(dòng)生成唯一標(biāo)識(shí)符,瀏覽器無(wú)法找到這些動(dòng)畫的定義,導(dǎo)致動(dòng)畫效果無(wú)法生效!
比較推薦的方法就是將動(dòng)畫樣式定義在全局樣式文件中,這樣可以確保 @keyframes
能夠被任何元素正確的引用使用。
以上就是Vue實(shí)現(xiàn)無(wú)限輪播效果時(shí)動(dòng)態(tài)綁定style失效的解決方法的詳細(xì)內(nèi)容,更多關(guān)于Vue動(dòng)態(tài)綁定style失效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue項(xiàng)目中引入字體文件的幾種方法總結(jié)
在 Vue 項(xiàng)目中引入自定義字體文件,可以通過(guò)多種方式實(shí)現(xiàn),這取決于你的項(xiàng)目結(jié)構(gòu)、構(gòu)建工具以及字體文件的來(lái)源,本文將詳細(xì)介紹如何通過(guò)不同方法引入本地字體文件以及從外部引入字體,需要的朋友可以參考下2024-10-10vue中實(shí)現(xiàn)點(diǎn)擊變成全屏及縮放功能
這篇文章主要介紹了vue中實(shí)現(xiàn)點(diǎn)擊變成全屏及縮放功能,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08vue項(xiàng)目實(shí)現(xiàn)圖形驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了vue項(xiàng)目實(shí)現(xiàn)圖形驗(yàn)證碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04vue代碼分割的實(shí)現(xiàn)(codesplit)
這篇文章主要介紹了vue代碼分割的實(shí)現(xiàn)(codesplit),做了代碼分割后,會(huì)將代碼分離到不同的bundle中,然后進(jìn)行按需加載這些文件,需要的朋友可以參考下2018-11-11