演示
几个参数说明
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里面定义