在大屏可视化应用中,滚动动效(跑马灯效果)也是常见的一种数据展现方式,本章节针对字幕滚动和列表滚动效果做一个小小的总结,结合vue框架,具体展示效果如下,从左至右选型技术分别为:marquee标签、css3-animation动画属性、 css3-animation动画属性、Javascript实现滚动动画

 文字滚动实现功能描述:

  1. 文字无限滚动展示,支持鼠标悬浮暂停功能
  2. 文字滚动速度可控
  3. 支持接口数据动态渲染,如果数据变化,动画重新开始

列表滚动实现功能描述:

  1. 列表无限滚动展示、支持鼠标悬浮暂停功能
  2. 列表滚动速度可控
  3. 支持接口数据动态渲染,数据变化,动画重新开始
  4. 支持鼠标滚动操作、可上滑至动画开始处或下滑至动画结束处
  5. 每页展示列表项数量可控

功能实现分析:

 常规动画类的实现效果,无非有三种方式:其一是借助原生html标签-marquee实现滚动效果;其二是通过css3相关动画属性实现动画效果;其三是使用javascript结合定时器,通过轮询实现相关属性动态变化的动画效果;当然每一种实现方式都有各自的优缺点,大家可以根据需要进行技术选型,本章针对相关实现原理进行简单阐述。

这里使用静态数据模拟接口数据,具体数据格式如下

// data.js
export let datas1 = `Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty中的
访问器属性中的 get和 set方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 
get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 
Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新
虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到
真实 DOM 树上`

export let datas2 = `《意见》明确,保障性租赁住房主要解决符合条件的新市民、青年人等群体的住房困难问题,
以建筑面积不超过70平方米的小户型为主,租金低于同地段同品质市场租赁住房租金;由政府给予政策支持,充分发挥
市场机制作用,引导多主体投资、多渠道供给,主要利用存量土地和房屋建设,适当利用新供应国有建设用地建设。
城市人民政府要坚持供需匹配,科学确定“十四五”保障性租赁住房建设目标和政策措施,制定年度建设计划,
并向社会公布;加强对保障性租赁住房建设、出租和运营管理的全过程监督,强化工程质量安全监管。
城市人民政府对本地区发展保障性租赁住房负主体责任,省级人民政府负总责。`

export let datas3 = [
    {
        content: "内容一",
        bgColor: "#08a6e9",
    },
    {
        content: "内容二",
        bgColor: "#22d4c8",
    },
    {
        content: "内容三",
        bgColor: "#ffa06a",
    },
    {
        content: "内容四",
        bgColor: "#0f96f0",
    },
    {
        content: "内容五",
        bgColor: "#ffa66a",
    }
];

export let datas4 = [
    {
        content: "content-1",
        bgColor: "#08a6e9",
    },
    {
        content: "content-2",
        bgColor: "#22d4c8",
    },
    {
        content: "content-3",
        bgColor: "#ffa06a",
    },
    {
        content: "content-4",
        bgColor: "#0f96f0",
    },
    {
        content: "content-5",
        bgColor: "#ffa66a",
    }
];

一、marquee标签实现文字滚动动画(对应图片第一个)

1.在组件中使用

<!-- 基于原生html-marquee标签实现滚动效果 -->
<template>
  <div class="container">
    <marquee
      scrollamount="4"
      hspace="0"
      vspace="0"
      behavior="scroll"
      direction="up"
    >
      <p class="content">{{ content }}</p>
    </marquee>
  </div>
</template>
<script>
import { datas1 } from "./data";
export default {
  data() {
    return {
      content: datas1,
    };
  },
};
</script>
<style scoped>
.container {
  width: 300px;
  height: 300px;
  padding: 10px;
  outline: 1px dashed #ccc;
  overflow: hidden;
}
.content {
  font-size: 14px;
  line-height: 30px;
  text-indent: 2em;
}
</style>

2.marquee标签的实现方式非常简单、只需要添加对应的属性即可,针对开发者非常友好。但是它是一个过时的标签、另外无法实现无缝滚动效果,难以应对复杂的需求场景、因此在项目中应用场景并不多。

二、css3-animation实现文字滚动效果(对应图片第二个)

1.首先封装一个文字滚动组件,代码如下

<template>
  <div class="font-wrap" ref="topbox">
    <div :class="['font-list', dynamicCls]" :style="dynamicSty">
      <div ref="fontbox" v-text="content"></div>
      <div v-if="doubleData" v-text="content"></div>
    </div>
  </div>
</template>
<script>
export default {
  name: "FontScrollIndex",
  props: {
    content: {
      default: "",
    },
    pageDuration: {
      default: 6,
    },
  },
  data() {
    return {
      doubleData: false, // 是否需要双份数据
      fontBox: null, // 文字容器
      topBox: null, // 文字父级容器
      countFlag: 0, // 切换clss类标识符
      dynamicCls: "", // 动态class类,切换使用解决数据更新后动画不重新开始问题
      dynamicSty: {},
    };
  },
  mounted() {
    this.fontBox = this.$refs.fontbox;
    this.topBox = this.$refs.topbox;
    this.$nextTick(() => {
      this.calcHeight();
    });
  },
  methods: {
    calcHeight() {
      // 判断内容是否超出容器,如果超出容器、追加一份数据
      if (this.fontBox.offsetHeight > this.topBox.offsetHeight) {
        this.doubleData = true;
        this.countFlag += 1;
        // 切换动画类名
        this.dynamicCls = `scroll-cls${this.countFlag % 2}`;
        // 动态计算动画时长
        this.dynamicSty = {
          animationDuration: `${
            (this.fontBox.offsetHeight / this.topBox.offsetHeight) *
            this.pageDuration
          }s`,
        };
      } else {
        this.doubleData = false;
        this.dynamicCls = "";
        this.dynamicSty = {
          animationDuration: "0s",
        };
      }
    },
  },
  watch: {
    // 监听内容变化
    content(val) {
      this.$nextTick(() => {
        this.calcHeight();
      });
    },
  },
};
</script>
<style scoped>
.font-wrap {
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.scroll-cls0 {
  animation: translateY1 8s 0.5s linear infinite;
}
.scroll-cls1 {
  animation: translateY2 8s 0.5s linear infinite;
}
.font-list:hover {
  animation-play-state: paused;
}
@keyframes translateY1 {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-50%);
  }
}
@keyframes translateY2 {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-50%);
  }
}
</style>

 2.组件调用

<!-- 基于CSS3-animation动画属性实现文字滚动效果-仅用于字幕滚动 -->
<template>
  <div class="container-wrapper">
    <!-- 滚动组件,如果需要给滚动组件文字添加样式,添加到container的div上即可 -->
    <div class="container">
      <!-- 组件接受两个参数,content:滚动的文字内容;pageDuration滚动一页需要的时间 -->
      <font-scroll :content="content" :pageDuration="10"></font-scroll>
    </div>
  </div>
</template>
<script>
import { datas1, datas2 } from "./data";
import FontScroll from "./FontScroll";
export default {
  components: { FontScroll },
  name: "FontScrollIndex",
  data() {
    return {
      content: datas1,
    };
  },
  mounted() {
    // 模拟数据发生变化
    setTimeout(() => {
      this.content = datas2;
    }, 10000);
  },
};
</script>
<style scoped>
.container-wrapper {
  display: flex;
  justify-content: space-around;
  width: 300px;
  height: 300px;
}
.container {
  width: 300px;
  height: 300px;
  padding: 10px;
  margin: 0 auto;
  outline: 1px dashed #ccc;
  font-size: 14px;
  line-height: 30px;
  text-indent: 2em;
}
</style>

3.使用animation实现稍微有点复杂,比如:数据列表数量时多时少,这就需要动态计算动画时长;其次实现无缝滚动需要动态计算内容高度,判断是否需要追加数据,双份数据或单份数据;另外当列表数据变化时列表动画重新开始也需要做特殊处理。但优势也非常明显,无需开发者关注动画性能问题、动画效果流畅、全程css控制仅需少量的javascript进行控制,改实现方式在实际开发中应用较多,推荐使用。

三、css3-animation实现列表滚动效果(对应图片第三个)

1.组件封装

<template>
  <div class="page-box">
    <!--
      为实现无缝滚动效果,需注意事项:
      1.内容容器(item-list)不留margin和padding
      2.数据列表项-li也不留margin和padding、紧挨排列
      3.将需要展示的内容放至class为item-content的标签下
    -->
    <ul :class="['item-list', dynamicCls]" :style="dynamicSty">
      <li v-for="(item, index) in itemList" :key="index">
        <div class="item-content" :style="{ 'background-color': item.bgColor }">
          {{ item.content }}
        </div>
      </li>
    </ul>
  </div>
</template>
<script>
import { datas3, datas4 } from "./data";
export default {
  name: "PageScroll",
  data() {
    return {
      singleDuration: 2, // 每一条滚动多久
      pageSize: 4, // 每页显示几个
      itemList: [], // 渲染数据列表
      dbData: datas3, // 模拟接口数据
      countFlag: 0, // 切换clss类标识符
      dynamicCls: "", // 动态class类,切换使用解决数据更新后动画不重新开始问题
      dynamicSty: {}, // 动态样式
    };
  },
  mounted() {
    this.calcAttr();
    // 模拟接口数据更新
    setTimeout(() => {
      this.dbData = datas4;
      this.calcAttr();
    }, 10000);
  },
  methods: {
    // 动态计算动画相关属性
    calcAttr() {
      let length = this.dbData.length;
      // 如果大于pageSize,则启用动画
      if (length > this.pageSize) {
        this.countFlag += 1;
        this.itemList = [...this.dbData, ...this.dbData];
        // 切换动画类名
        this.dynamicCls = `scroll-box${this.countFlag % 2}`;
        // 动态设置样式-开启动画
        this.dynamicSty = {
          height: (100 / this.pageSize) * length * 2 + "%", // 动态计算容器高度
          "animation-iteration-count": "infinite",
          "animation-duration": `${length * this.singleDuration}s`,
        };
      } else {
        this.itemList = this.dbData;
        // 动态设置样式-关闭动画
        this.dynamicSty = {
          height: (100 / this.pageSize) * length + "%", // 动态计算容器高度
          "animation-iteration-count": "0",
          "animation-duration": "0s",
        };
      }
    },
  },
};
</script>
<style scoped>
.page-box {
  width: 300px;
  height: 300px;
  overflow: hidden;
}
.item-list {
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  animation-timing-function: linear;
}

.item-list:hover {
  animation-play-state: paused;
}

.item-list > li {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.item-content {
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  color: #fff;
  width: 80%;
  height: 80%;
  border-radius: 6px;
}
.scroll-box0 {
  animation: box-move0 8s 0.5s linear infinite;
}
.scroll-box1 {
  animation: box-move1 8s 0.5s linear infinite;
}
@keyframes box-move0 {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-50%);
  }
}
@keyframes box-move1 {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-50%);
  }
}
</style>

 2.列表滚动-css3实现方式和css3-文字版实现原理大致一样,具体实现方式可查看以上代码和备注文字描述。该方式是通过列表容器位移实现的,适应于大部分的场景。如果有特殊需求,比如:可通过鼠标滑动列表,就需要通过javascript控制了。

四、Javascript实现滚动效果(对应图片第四个)

1.该实现方案是在“案例三”的基础上增加了鼠标滚轮滑动操作,通过动态改变scrollTop属性实现滚动展示,相比css控制,这种控制方式具有更强的可塑性

2.代码如下:

<!-- 基于javaScript scrollTop实现滚动效果 -->
<template>
  <div class="container" @mouseenter="stopScroll" @mouseleave="startScroll">
    <!--
      为实现无缝滚动效果,需注意事项:
      1.内容容器(container-wrapper)不留margin和padding
      2.数据列表项-li也不留margin和padding、紧挨排列
      3.将需要展示的内容放至class为item-content的标签下
    -->
    <ul class="container-wrapper" ref="box">
      <li v-for="(item, index) in itemList" :key="index">
        <div class="item-content" :style="{ 'background-color': item.bgColor }">
          {{ item.content }}
        </div>
      </li>
    </ul>
  </div>
</template>
<script>
import { datas3, datas4 } from "./data";
export default {
  data() {
    return {
      box: null, // 内容容器
      Timer: null, // 记录定时器id
      dbData: datas3, // 模拟接口数据
      itemList: [], // 渲染数据列表
      pageSize: 4, // 每页显示几个
    };
  },
  mounted() {
    this.box = this.$refs.box;
    this.calcAttr();
    // 模拟接口数据更新
    setTimeout(() => {
      this.dbData = datas4;
      this.calcAttr();
    }, 10000);
  },
  beforeDestroy() {
    // 组件销毁前务必关闭动画,释放资源
    this.stopScroll();
  },
  methods: {
    calcAttr() {
      // 关闭滚动动画
      this.stopScroll();
      // 动画重新开始
      this.box.parentNode.scrollTop = 0;
      // 根据数据长度及每页展示数量,动态计算容器高度
      let length = this.dbData.length;
      if (length > this.pageSize) {
        // 超过一页,需要给与双份数据
        this.box.style.height = `${(100 / this.pageSize) * length * 2}%`;
        this.itemList = [...this.dbData, ...this.dbData];
      } else {
        // 不超过一页,单份数据
        this.box.style.height = `${(100 / this.pageSize) * length}%`;
        this.itemList = this.dbData;
      }
      // 务必等待dom重新渲染之后执行滚动动画
      this.$nextTick(() => {
        this.startScroll();
      });
    },
    startScroll() {
      this.stopScroll();
      // 判断是否滚动了一半,如果超过一半,拉回去重新开始
      if (this.box.offsetHeight > this.box.parentNode.offsetHeight) {
        this.box.parentNode.scrollTop++;
        if (this.box.parentNode.scrollTop >= this.box.offsetHeight / 2) {
          this.box.parentNode.scrollTop = 0;
        }
        // 定时滚动
        this.Timer = requestAnimationFrame(this.startScroll);
      }
    },
    stopScroll() {
      if (this.Timer) {
        cancelAnimationFrame(this.Timer);
        this.Timer = null;
      }
    },
  },
};
</script>
<style scoped>
.container {
  width: 300px;
  height: 300px;
  outline: 1px solid #ccc;
  overflow: auto;
}
.container-wrapper {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  list-style: none;
}
.container-wrapper > li {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.item-content {
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 14px;
  color: #fff;
  width: 80%;
  height: 80%;
  border-radius: 6px;
}
</style>

总结:每一种实现方案都有自己的使用场景,大家可以结合实际需求进行技术选型,掌握了滚动动画实现的原理,就可以在实际开发中得心应手。

Logo

前往低代码交流专区

更多推荐