VUE封装一个文本滚动组件

3,160 阅读2分钟

一、引言

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

image.png

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

二、实现思路

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

给滚动元素设置绝对定位并给他的父元素设置相对定位,定义如:left: a => left: b 的动画来实现滚动效果。

2.组件需要哪些配置?

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

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

(3)是否强制滚动:非强制滚动时若内容未超出容器则不滚动

三、实现过程

1.html

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

2.css

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

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

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

3.动画

动画设置在滚动区

需要往上右下左四个不同方向滚动的动画,我们只需要定义上左两个方向另外两个方向直接反转即可。先来看向上滚动的动画怎么写,向上滚动的话滚动区的起点应设置在可视区外正下方,所以动画的起点应为 top: 100%,终点应设置在可视区外正上方应为top: 负的滚动区高度。

image.png

向上向左动画即为:

<style lang="scss">
.text-scroll {
  .content {
    @keyframes up-scroll {
      0% {
        top: 100%;
      }

      100% {
        top: var(--animation-end);
      }
    }

    @keyframes left-scroll {
      0% {
        left: 100%;
      }

      100% {
        left: var(--animation-end);
      }
    }
  }
}
</style>

--animation-end这个变量的值是负的滚动区的宽度或高度,具体是宽度还是高度由滚动的方向决定,由于滚动区的宽高不确定,所以需要通过js获取并设置这个变量。

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

4.js

(1)组件配置

props: {
  /* 滚动方向
   * value: up、down、left、right
   */
  direction: {
    default: "up",
    type: String,
  },
  //滚动速度 单位px/s
  speed: {
    default: 60,
    type: Number,
  },
  /* 是否强制滚动
   * false: 内容未超出时不滚动
   */
  isForceScroll: {
    default: true,
    type: Boolean,
  },
},

通过props传入组件配置

(2)设置滚动动画

data() {
  return {
    contentStyle: {
      //滚动动画
      "--animation-end": "",
      animation: "",
    },
  };
},
methods: {
  //设置滚动动画
  setScrollAnimation() {
    //获取文本滚动实际显示宽度高度以及滚动内容宽度高度
    let textScrollWidth = this.$refs.textScroll.offsetWidth,
      textScrollHeight = this.$refs.textScroll.offsetHeight,
      contentWidth = this.$refs.content.offsetWidth,
      contentHeight = this.$refs.content.offsetHeight;
    //不强制滚动判断内容是否超出,未超出则不滚动
    if (!this.isForceScroll) {
      if (this.direction === "up" || this.direction === "down") {
        if (contentHeight <= textScrollHeight) {
          this.contentStyle["--animation-end"] = "";
          this.contentStyle.animation = "";
          return;
        }
      } else {
        if (contentWidth <= textScrollWidth) {
          this.contentStyle["--animation-end"] = "";
          this.contentStyle.animation = "";
          return;
        }
      }
    }
    //滚动长度、时间
    let scrollLength, time;
    //根据滚动方向来设置不同的滚动动画
    switch (this.direction) {
      case "up":
        this.contentStyle["--animation-end"] = `-${contentHeight}px`;
        scrollLength = contentHeight + textScrollHeight;
        time = scrollLength / this.speed;
        this.contentStyle.animation = `up-scroll linear ${time}s infinite`;
        break;
      case "down":
        this.contentStyle["--animation-end"] = `-${contentHeight}px`;
        scrollLength = contentHeight + textScrollHeight;
        time = scrollLength / this.speed;
        this.contentStyle.animation = `up-scroll linear ${time}s infinite reverse`;
        break;
      case "left":
        this.contentStyle["--animation-end"] = `-${contentWidth}px`;
        scrollLength = contentWidth + textScrollWidth;
        time = scrollLength / this.speed;
        this.contentStyle.animation = `left-scroll linear ${time}s infinite`;
        break;
      case "right":
        this.contentStyle["--animation-end"] = `-${contentWidth}px`;
        scrollLength = contentWidth + textScrollWidth;
        time = scrollLength / this.speed;
        this.contentStyle.animation = `left-scroll linear ${time}s infinite reverse`;
        break;
    }
  },
},

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

(3)生命周期

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

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

四、在线演示

五、完整代码

<template>
  <!-- 文本滚动 -->
  <div class="text-scroll" ref="textScroll">
    <div class="content" ref="content" :style="contentStyle">
      <!-- 默认插槽,插入滚动内容 -->
      <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,
    },
    /* 是否强制滚动
     * false: 内容未超出时不滚动
     */
    isForceScroll: {
      default: true,
      type: Boolean,
    },
  },
  data() {
    return {
      contentStyle: {
        //滚动动画
        "--animation-end": "",
        animation: "",
      },
    };
  },
  methods: {
    //设置滚动动画
    setScrollAnimation() {
      //获取文本滚动实际显示宽度高度以及滚动内容宽度高度
      let textScrollWidth = this.$refs.textScroll.offsetWidth,
        textScrollHeight = this.$refs.textScroll.offsetHeight,
        contentWidth = this.$refs.content.offsetWidth,
        contentHeight = this.$refs.content.offsetHeight;
      //不强制滚动判断内容是否超出,未超出则不滚动
      if (!this.isForceScroll) {
        if (this.direction === "up" || this.direction === "down") {
          if (contentHeight <= textScrollHeight) {
            this.contentStyle["--animation-end"] = "";
            this.contentStyle.animation = "";
            return;
          }
        } else {
          if (contentWidth <= textScrollWidth) {
            this.contentStyle["--animation-end"] = "";
            this.contentStyle.animation = "";
            return;
          }
        }
      }
      //滚动长度、时间
      let scrollLength, time;
      //根据滚动方向来设置不同的滚动动画
      switch (this.direction) {
        case "up":
          this.contentStyle["--animation-end"] = `-${contentHeight}px`;
          scrollLength = contentHeight + textScrollHeight;
          time = scrollLength / this.speed;
          this.contentStyle.animation = `up-scroll linear ${time}s infinite`;
          break;
        case "down":
          this.contentStyle["--animation-end"] = `-${contentHeight}px`;
          scrollLength = contentHeight + textScrollHeight;
          time = scrollLength / this.speed;
          this.contentStyle.animation = `up-scroll linear ${time}s infinite reverse`;
          break;
        case "left":
          this.contentStyle["--animation-end"] = `-${contentWidth}px`;
          scrollLength = contentWidth + textScrollWidth;
          time = scrollLength / this.speed;
          this.contentStyle.animation = `left-scroll linear ${time}s infinite`;
          break;
        case "right":
          this.contentStyle["--animation-end"] = `-${contentWidth}px`;
          scrollLength = contentWidth + textScrollWidth;
          time = scrollLength / this.speed;
          this.contentStyle.animation = `left-scroll linear ${time}s infinite reverse`;
          break;
      }
    },
  },
  mounted() {
    this.setScrollAnimation();
  },
  updated() {
    //当插槽内容更新重新设置滚动动画
    this.setScrollAnimation();
  },
};
</script>

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

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

<style lang="scss">
.text-scroll {
  .content {
    @keyframes up-scroll {
      0% {
        top: 100%;
      }

      100% {
        top: var(--animation-end);
      }
    }

    @keyframes left-scroll {
      0% {
        left: 100%;
      }

      100% {
        left: var(--animation-end);
      }
    }
  }
}
</style>