自定义marquee组件 文本过长实现横向无限滚动

演示

在这里插入图片描述

在这里插入图片描述

几个参数说明

在这里插入图片描述
word:父组件传入的文字
startWidth: 这一段距离 单位px
在这里插入图片描述
endWidth:这一段距离 单位px
在这里插入图片描述

step,interval : 每interval毫秒移动多少px(step)

思路

在这里插入图片描述
container 的宽度和高度由父组件的容器决定
content就是包裹文字的容器,
在这里插入图片描述

拿到content宽度以后,需要判断是不是超过container的宽度, 赋值给isOverflow;
拿到container高度是为了让文字的line-height=container高度 来让他垂直居中
初始化布局
在这里插入图片描述
在这里插入图片描述

接下来看滚动的逻辑
第二个什么时候可以进入到container呢
这个是根据offset来计算的
在这里插入图片描述
run方法
在这里插入图片描述
第二个移动多少重新进行一次循环呢
在这里插入图片描述
在这里插入图片描述
移动到这里 ,重新进行一次循环,刚好初始化第一个的offset为-(this.containerWidth - this.startWidth);
这样的话到达start这里以后,看着是第二个在移动,其实是已经循环完了,是第一个在移动

在这里插入图片描述

代码

父组件

<!--  -->
<template>
  <div class="root">
      <marquee :word=word3  :interval="100" :step="5" :endWidth="20" style="margin:0 auto"></marquee>
  </div>
</template>

<script>
import Marquee from '../components/marqueeCpn.vue';

export default {
      
      
  components:{
      
      
    Marquee
  },
  data() {
      
      
    return {
      
      
      word1:'short',
      word2:'longlonglong我是一段很长的文字很长的文字很长的文字',
      word3:'稍微长一点的文字文字文字文字文字'
    };
  },
  //生命周期 - 创建完成(访问当前this实例)
  created() {
      
      },
  //生命周期 - 挂载完成(访问DOM元素)
  mounted() {
      
      },
};
</script>
<style>
.root{
      
      
  margin: 0 auto;
  width: 200px;
  height: 60px;
  margin: 50px;
}
</style>

marquee组件

<!--  -->
<template>
  <div class="container" :style="containerStyle">
    <div class="content" :style="contentStyle">{
   
   { word }}</div>
    <div v-if="isOverflow" class="contentcp" :style="contentcpStyle">
      {
   
   { word }}
    </div>
  </div>
</template>

<script>
let interval=null
export default {
      
      
  props: {
      
      
    word: String,
    // 开始滚动时距离左边间距
    startWidth: {
      
      
      type: Number,
      default: 20,
    },
    // 距离右边多少距离开始循环下一跳
    endWidth: {
      
      
      type: Number,
      default: 100,
    },
    // 每interval秒滚动多少px
    step: {
      
      
      type: Number,
      default: 5,
    },
    // 每多少毫秒滚动一次
    interval: {
      
      
      type: Number,
      default: 100,
    },
  },
  
  data() {
      
      
    return {
      
      
      // 字的长度是否超出父盒子
      isOverflow: false,
      // 偏移量
      offset: 0,
      // 拷贝的那一份的文本的偏移量
      cpOffset: 0,
      // 垂直居中要用到
      containerHeight: "",
      // 盒子宽度
      containerWidth: 0,
      // 内容宽度
      contentWidth: 0,
    };
  },
  mounted() {
      
      
    this.$nextTick(() => {
      
      
      // console.log(document.getElementsByClassName("container"));
      const containerWidth =
        document.getElementsByClassName("container")[0].clientWidth;
      const contentWidth =
        document.getElementsByClassName("content")[0].clientWidth;
      const containerHeight =
        document.getElementsByClassName("container")[0].clientHeight;
      if (containerWidth >= contentWidth) {
      
      
        this.isOverflow = false;
      } else {
      
      
        this.isOverflow = true;
      }
      // console.log(containerHeight);
      this.containerHeight = containerHeight;
      this.containerWidth = containerWidth;
      this.contentWidth = contentWidth;
      if (this.containerWidth < this.contentWidth) {
      
      
        this.isOverflow = true;
        const startWidth = this.startWidth;
        this.offset = -(this.containerWidth - startWidth);
        this.run();
      } else {
      
      
        this.isOverflow = false;
      }
    });
  },
  computed: {
      
      
    containerStyle() {
      
      
      return {
      
      };
    },
    // 初始化布局
    contentStyle() {
      
      
      if (!this.isOverflow) {
      
      
        return {
      
      
          textAlign: "center",
          lineHeight: this.containerHeight + "px",
        };
      } else {
      
      
        // console.log(`translate3d(${this.offset}px,0,0)`);
        return {
      
      
          transform: `translate3d(${ 
        this.offset}px,0,0)`,
          left: this.containerWidth + "px",
          lineHeight: this.containerHeight + "px",
        };
      }
    },
    contentcpStyle() {
      
      
      if (!this.isOverflow) {
      
      
        return;
      }
      return {
      
      
        left: this.containerWidth + "px",
        top: -this.containerHeight + "px",
        lineHeight: this.containerHeight + "px",
        transform: `translate3d(${ 
        this.cpOffset}px,0,0)`,
      };
    },
    // 第二个能进入container需要移动的距离
    openCpBox() {
      
      
      if (!this.isOverflow) {
      
      
        return;
      }
      return this.contentWidth + this.endWidth;
    },
    // 第二个能动的判断
    isOpenCpbox() {
      
      
      if (!this.isOverflow) {
      
      
        return;
      }
      return this.offset <= -this.openCpBox;
    },
    // 走完一轮的路程
    // totalDistance() {
      
      
    //   if (!this.isOverflow) {
      
      
    //     return 0;
    //   }
    //   if (this.cpOffset!=0) {
      
      
    //     // console.log(-this.cpOffset - (this.contentWidth - this.startWidth));
    //     return (
    //       -this.cpOffset - (this.contentWidth - this.startWidth) < this.step
    //     );
    //   }
    // },

    // contentRightToContainerRight() {
      
      
    //   // 第二个开始进入container逻辑
    //   if (Math.abs(this.offset - this.openCpBox) <= this.step) {
      
      
    //     console.log("第二个开始进入container逻辑");
    //     return true;
    //   }
    //   return false;
    // },
    // 根据第二个文字移动的距离判断时候第一轮已经循环完
    isToEnd() {
      
      
      if (this.containerWidth-this.startWidth+this.cpOffset<this.step) {
      
      
        return true;
      }
      return false;
    },
  },
  methods: {
      
      
    run() {
      
      
     interval = setInterval(() => {
      
      
        this.offset = this.offset - this.step;
        let offsetVal = this.offset;
        // 达到了第二个能动的条件
        if (this.isOpenCpbox) {
      
      
          this.cpOffset = this.cpOffset - this.step;
        }
        // console.log(this.cpOffset, "   ", this.totalDistance);
        // 如果第二个到达了末尾 那么重新开定时器
        if (this.isToEnd) {
      
      
          clearInterval(interval);
          this.offset = -(this.containerWidth - this.startWidth);
          this.cpOffset = 0;
          this.run();
        }
      }, this.interval);
    },
  },
  //生命周期 - 创建完成(访问当前this实例)
  created() {
      
      },
  //生命周期 - 挂载完成(访问DOM元素)
  unmounted() {
      
      
    clearInterval(interval)
  },
};
</script>
<style>
.container {
      
      
  height: 100%;
  border: 1px solid #333;
  text-align: center;
  overflow: hidden; 
}
.content {
      
      
  position: relative;

  white-space: nowrap;
  display: inline-block;
}
.contentcp {
      
      
  position: relative;
  white-space: nowrap;
  display: inline-block;
}
</style>

有个疑问

在同一个页面里面,如何使用两次这个组件,因为mounted只运行了一次,所以只拿了一次元素高度和宽度
有空把mounted里面的代码改成用watch 还有computed写 看看能不能把行
https://www.cnblogs.com/yanggb/p/12431367.html
现在暂时是建两个实例
在这里插入图片描述
但是注意有个地方的代码需要改
interval不能设成全局的了,不然会相互影响
在data里面定义
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/xiaozhazhazhazha/article/details/121690728