vue实现无限轮播效果时动态绑定style失效
前言
最近在开发中遇到了一个新需求:列表轮播滚动;这个需求其实是比较常见的,实现方式也有很多,比如使用第三方插件:vue-seamless-scroll
,但是由于不想依赖第三方插件,想自己实现,于是我开始了尝试,但是在这个过程中遇到了动态绑定style样式不生效、绑定值与渲染值不一致等问题。谨以此篇记录自己的学习过程,也希望可以帮助到其他同学。
基础代码如下
创建一个 scroll.vue
组件,内容如下:
- 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>
实现方式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 高度,这通常是单行的高度,用于后续计算滚动的位移。 -
获取
data
数组中的第一个元素,这个元素将在滚动时移动到列表的底部 -
第一个
setTimeout
延迟 500 毫秒后执行以下操作:
this.data.push(shift)
:将第一个元素添加到data
数组的末尾,模拟循环移动。tabDom.style.transition = "all .5s"
:设置过渡效果为 0.5 秒,表示 marginTop 的变化将有动画效果。tabDom.style.marginTop =
-${rowOffsetHeight}px`` :将tabDom
的上边距设置为负的行高度,造成看似向上滚动的效果。
-
第二个
setTimeout
延迟 1000 毫秒后执行以下操作:this.data.splice(0, 1)
:移除data
数组中的第一个元素,完成滚动。tabDom.style.transition = "all 0s ease 0s"
:取消过渡效果,确保下次滚动时没有延迟。tabDom.style.marginTop = "0"
:将 marginTop 重置为 0,以准备下一次滚动。
主要是通过不停的将第一条数据添加到数组末尾、然后删除当前第一条数据,同时添加删除动画样式来实现滚动效果。这种方式添加删除时会出现停顿卡顿的现象。
实现方式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;
}
},
首先通过 this.$el.querySelector(".scroll-content");
获取到目标dom元素,如果该dom存在,获取其 offsetHeight
值。
然后调用 setScrollClass
方法传参:
- offsetHeight / 2是dom的一半高度
- speed 轮播速度
最后将返回值设置给全局变量 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}`;
}
该函数主要职责:
- 使用
document.createElement("style")
方法创建一个style标签并插入@keyframes
动画、class
- 最终获取
head
��签将生成的style
标签插入 - 返回生成的好的动画
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: [
"机构类",
"区域类",
"国家类",
"证照文书类",
"业务对象类",
"人员类",
"法律法规类型",
"金融类"
]
}
}
需要注意的是这里需要将原数据拷贝成两份数据,我这里使用计算属性+ 扩展运算符实现 scrollList
方法返回双份数据。
开始优化
在上面方式1中的代码已经可以实现dom列表的无限滚动效果;但是可以看出来该代码有优化的空间。
- 动态插入css动画
- 动态创建标签插入style
修改基础代码
添加: 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动画添加到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 通过
this.$refs.scrollContent
直接获取组件的 DOM 元素 -
计算偏移高度
offsetHeight
:获取scrollContent
元素的高度,并将其除以 2,计算出需要滚动的高度。这里因为动画只需要滚动一半的内容,因为我们的数据复制了两份。 -
计算动画时间
duration
: 根据offsetHeight
和speed
计算动画的持续时间,公式为:offsetHeight * 1000
:将高度转换为毫秒(假设speed
是以像素/秒为单位)Math.floor
:向下取整,确保持续时间是一个整数。
动画参数
animation
设置 CSS 动画的属性:
${duration}ms
:指定动画持续时间(毫秒)。listRowup
:应为 CSS 中定义的动画名称,表示动画的具体效果。linear
:表示动画的速度曲线是线性的。infinite
:表示动画将无限循环。normal
:表示动画的播放方向为正常方向(从开始到结束)。
遇到问题
当在 mounted
中执行 dynamicStyle
方法时发现动画没有生效,检查元素后发现确实成功绑定动画了,但是为什么不生效呢?
于是将结果值与dom元素打印出来做对比:
发现动态绑定的style
animation
的属性结果与预期(函数设置的style)绑定的值竟然不同! 我一脸懵逼,反复查看代码发现没有错误啊!
尝试解决
-
mounted
中使用$nextTick(function(){})
方法无效 -
修改@keyframes动画名称无效
最终解决
- 动画css单独写一个style标签,不添加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>
经过查资料分析发现原来是因为 style
标签的 scoped
属性导致的动画样式的选择器不匹配
在 scoped
样式中生成的类名会自动生成唯一标识符,浏览器无法找到这些动画的定义,导致动画效果无法生效!
比较推荐的方法就是将动画样式定义在全局样式文件中,这样可以确保 @keyframes
能够被任何元素正确的引用使用。
本文内容就这么多,希望对你有所帮助!
转载自:https://juejin.cn/post/7399100662611968011