VUE封装一个文本滚动组件

lxf2023-03-20 12:09:02

一、引言

项目有文本滚动展示的需求,一开始使用marquee标签来实现需求,但是看一下MDN对该标签的描述:

VUE封装一个文本滚动组件

这个标签已经不再推荐使用了,那就自己封装一个类似的组件来用。

二、实现思路

1.如何让文本滚动起来?

通过动画我们设置过渡transform:translateX(a) => transfrom:translateX(b)来实现向左或向右的滚动效果。transform:translateY(c) => transfrom:translateY(d)来实现向下或向上的滚动效果。

2.组件需要哪些配置

(1)滚动的方向:上右下左

(2)滚动的速度:这里我以px/s作为单位

三、实现过程

1.html

<template>
  <!-- 文本滚动 -->
  <div class="text-scroll" ref="textScroll">
    <div class="content" ref="content" :style="scrollAnimation">
      <!-- 默认插槽,插入滚动内容 -->
      <slot></slot>
    </div>
  </div>
</template>

2.css

<style scoped lang="scss">
.text-scroll {
  width: 100%;
  height: 100%;
  overflow: hidden;

  .content {
    height: fit-content;
    width: fit-content;
  }
}
</style>

最外层的div为滚动的可视区,里面的div为文本滚动区可视区宽度高度均为100%使用时大小由外部容器决定,overflow设为hidden,防止文本滚动区滚动出可视区外仍可见。文本滚动区设置宽高都为fit-content使大小随内容自适应,内部设置插槽使用组件时插入滚动内容。

3.动画

动画应设置在文本滚动区

需要上右下左四个方向滚动的动画,我们只需要定义上左两个方向,另外两个方向直接反转即可。先来想想向上滚动的动画怎么写,向上滚动的话文本滚动区的起点应设置在可视区外正下方,所以动画的起点应为 transform: translateY(可视区的高度),终点应设置在可视区外正上方应为transform: translateY(-100%)。

VUE封装一个文本滚动组件

向上向左动画即为:

<style lang="scss">
.text-scroll {
  .content {
    @keyframes up-scroll {
      0% {
        transform: translateY(var(--text-scroll-height));
      }

      100% {
        transform: translateY(-100%);
      }
    }

    @keyframes left-scroll {
      0% {
        transform: translateX(var(--text-scroll-width));
      }

      100% {
        transform: translateX(-100%);
      }
    }
  }
}
</style>

--text-scroll-height、--text-scroll-width两个css变量的值是可视区的高度和宽度,由于可视区的宽高不确定,所以需要通过js获取并设置这两个css变量。

注意: 动画样式不能加上scoped,否则不生效!

4.js

(1)组件配置

props: {
  /* 滚动方向
   * value: up、down、left、right
   */
  direction: {
    default: "up",
    type: String,
  },
  //滚动速度 单位px/s
  speed: {
    default: 60,
    type: Number,
  },
},

通过props传入组件配置

(2)计算得到滚动动画

methods: {
  getScrollAnimation() {
    //获取文本滚动实际显示宽度高度,设为css变量,用于设置动画开始起始位置
    let height = this.$refs.textScroll.offsetHeight;
    let width = this.$refs.textScroll.offsetWidth;
    this.$refs.content.style.setProperty(
      "--text-scroll-height",
      `${height}px`
    );
    this.$refs.content.style.setProperty("--text-scroll-width", `${width}px`);
    //滚动长度、时间
    let scrollLength, time;
    //根据滚动方向来设置不同的滚动动画
    switch (this.direction) {
      case "up":
        scrollLength =
          this.$refs.content.offsetHeight +
          height;
        time = scrollLength / this.speed;
        this.scrollAnimation.animation = `up-scroll linear ${time}s infinite`;
        break;
      case "down":
        scrollLength =
          this.$refs.content.offsetHeight +
          height;
        time = scrollLength / this.speed;
        this.scrollAnimation.animation = `up-scroll linear ${time}s infinite reverse`;
        break;
      case "left":
        scrollLength =
          this.$refs.content.offsetWidth + width;
        time = scrollLength / this.speed;
        this.scrollAnimation.animation = `left-scroll linear ${time}s infinite`;
        break;
      case "right":
        scrollLength =
          this.$refs.content.offsetWidth + width;
        time = scrollLength / this.speed;
        this.scrollAnimation.animation = `left-scroll linear ${time}s infinite reverse`;
        break;
    }
  },
},

scrollLength为一次动画的实际滚动的长度,time为一次动画的持续时间。我们可以算出scrollLength应为:可视区的宽或高加上文本滚动区的宽或高(根据滚动的方向来判断是宽还是高)。time应为 scrollLegnth / 组件配置的滚动速度。

(3)生命周期

在mounted里调用上面的方法初始化组件。在updated里同样调用该方法,当组件宽高改变或插槽内容变动重新计算动画样式。

mounted() {
  //设置文本滚动动画
  this.getScrollAnimation();
},
updated() {
  //当插槽内容更新重新计算滚动动画
  this.getScrollAnimation();
},

三、完整代码

<template>
  <!-- 文本滚动 -->
  <div class="text-scroll" ref="textScroll">
    <div class="content" ref="content" :style="scrollAnimation">
      <!-- 默认插槽,插入滚动内容 -->
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "TextScroll",
  props: {
    /* 滚动方向
     * value: up、down、left、right
     */
    direction: {
      default: "up",
      type: String,
    },
    //滚动速度 单位px/s
    speed: {
      default: 60,
      type: Number,
    },
  },
  data() {
    return {
      //滚动动画
      scrollAnimation: {},
    };
  },
  methods: {
    getScrollAnimation() {
      //获取文本滚动实际显示宽度高度,设为css变量,用于设置动画开始起始位置
      let height = this.$refs.textScroll.offsetHeight;
      let width = this.$refs.textScroll.offsetWidth;
      this.$refs.content.style.setProperty(
        "--text-scroll-height",
        `${height}px`
      );
      this.$refs.content.style.setProperty("--text-scroll-width", `${width}px`);
      //滚动长度、时间
      let scrollLength, time;
      //根据滚动方向来设置不同的滚动动画
      switch (this.direction) {
        case "up":
          scrollLength =
            this.$refs.content.offsetHeight +
            height;
          time = scrollLength / this.speed;
          this.scrollAnimation.animation = `up-scroll linear ${time}s infinite`;
          break;
        case "down":
          scrollLength =
            this.$refs.content.offsetHeight +
            height;
          time = scrollLength / this.speed;
          this.scrollAnimation.animation = `up-scroll linear ${time}s infinite reverse`;
          break;
        case "left":
          scrollLength =
            this.$refs.content.offsetWidth + width;
          time = scrollLength / this.speed;
          this.scrollAnimation.animation = `left-scroll linear ${time}s infinite`;
          break;
        case "right":
          scrollLength =
            this.$refs.content.offsetWidth + width;
          time = scrollLength / this.speed;
          this.scrollAnimation.animation = `left-scroll linear ${time}s infinite reverse`;
          break;
      }
    },
  },
  mounted() {
    //设置文本滚动动画
    this.getScrollAnimation();
  },
  updated() {
    //当插槽内容更新重新计算滚动动画
    this.getScrollAnimation();
  },
};
</script>

<style scoped lang="scss">
.text-scroll {
  width: 100%;
  height: 100%;
  overflow: hidden;

  .content {
    height: fit-content;
    width: fit-content;
  }
}
</style>

<style lang="scss">
.text-scroll {
  .content {
    @keyframes up-scroll {
      0% {
        transform: translateY(var(--text-scroll-height));
      }

      100% {
        transform: translateY(-100%);
      }
    }

    @keyframes left-scroll {
      0% {
        transform: translateX(var(--text-scroll-width));
      }

      100% {
        transform: translateX(-100%);
      }
    }
  }
}
</style>