实现思路与分析
飘窗一般用于页面上通知信息,互动性和显眼度高一些.飘窗顾名思义是一个会动的窗口,一般是从屏幕一侧飘至另一侧,接触到窗口边框时则会反弹至另一边,并伴随有鼠标移入移出的效果.所以实现飘窗需要做到以下功能:
1.绘制矩形飘窗框,固定位置
<div :class="inTime ? '' : 'none'">
<div v-if="show" @mouseover="mouseover" @mouseout="mouseout" class="box" :style="{ top: top + 'px', left: left + 'px' }">
<span @click="close" style="color: white" class="closeBtn">关闭</span>
<div class="info-text">{{ floatWindowContent }}</div>
</div>
</div>
飘窗嘛先随便写个盒子加上你想要的样式就ok了,完事整上position:fixed,利用固定定位使飘窗在页面窗口上移动
2.设定飘窗移动规则
1.设置最大top和left
默认两个0就不说了哈,所以最大的maxtTop\maxLeft=需要可视区域宽高-飘窗宽高-边距
这一块使用document来写 :
//设置最大值
this.maxTop = document.documentElement.clientHeight - 150 - 20;
this.maxLeft = document.documentElement.clientWidth - 200 - 20;
//设置初始值
this.top = 0;
this.left = 0;
//后续使用的时候增加定时器修改top和left的值,此处仅为初始化
2.左右漂浮设置位置
之前我们已经用position了,所以后面我们用top和left的位置就可以确定坐标了,所以需要设置每一次移动的距离,为了让操作看起来比较流畅,所以设置移动距离stepX和stepY都为1. 当遇到边界时将stepX/stepY分别设为相反数,水平就设置-stepX,垂直就设置为-stepY:
if (this.top >= this.maxTop || this.top < 0) {
this.stepY = -this.stepY;
}
if (this.left >= this.maxLeft || this.left < 0) {
this.stepX = -this.stepX;
}
this.top += this.stepY;
this.left += this.stepX;
}
3.鼠标悬浮飘窗上禁止移动
悬浮的时候利用的是onmouseover时间, 还记得初始化的时候备注里提到设置的定时器吗? 禁止移动的时候清除定时器,top和left不改变 即为 停止飘窗移动
/
// 鼠标悬浮在飘窗时停止移动
mouseover() {
clearInterval(this.timer);
},
4.鼠标离开飘窗时继续移动
同上此处利用onmouseout时间,将定时器找回来就ok了
注意! 开启下一个定时器之前要确保之前的定时器已经被清除掉了,保证只有一个定时器能让飘窗移动才行,这样才能好好的延续路线
// 鼠标离开飘窗时恢复移动
mouseout() {
clearInterval(this.timer);
this.timer = setInterval(() => {
this.move();
}, 20);
},
5.关闭飘窗
老发言了,清理定时器,关掉设置显示的v-show
// 关闭飘窗
close() {
clearInterval(this.timer);
this.show = false;
},
6.定时显示
项目需求,要求弹窗只在某一个时间段显示即可,所以增加了对时间的判断,设置开始/结束时间,加载页面的时候判断是否在时间内决定是否显示
checkInTime() {
let date = new Date().getTime();
let startTime = new Date(this.floatWindowStartTimer).getTime(),
endTime = new Date(this.floatWindowEndTimer).getTime();
if (date >= startTime && date <= endTime) {
this.inTime = true;
}
}
//如果时间需要把握的非常谨慎,到点就关可以设置个定时器吧?
完整DEMO
<template>
<div :class="inTime ? '' : 'none'">
<div v-if="show" @mouseover="mouseover" @mouseout="mouseout" class="box" :style="{ top: top + 'px', left: left + 'px' }">
<span @click="close" style="color: white" class="closeBtn">关闭</span>
<div class="info-text">{{ floatWindowContent }}</div>
</div>
</div>
</template>
<script>
import { format } from "date-fns";
export default {
name: "MoveWindow",
data() {
return {
inTime: false,
show: true, // 是否展现飘窗
stepX: 1, // 水平方向的步长
stepY: 1, // 垂直方向的步长
timer: "", // 定时器
maxTop: 0, // 最大的 top 值
maxLeft: 0, // 最大的 left 值
top: 0,
left: 0,
floatWindowContent: "",
floatWindowStartTimer: "",
floatWindowEndTimer: ""
};
},
mounted() {
this.init();
this.floatWindowContent = "新年快乐";
// 飘窗起始时间
this.floatWindowStartTimer = "2024/02/01 00:00:00";
this.floatWindowEndTimer = "2024/02/18 0:00:00";
this.checkInTime();
},
beforeDestroy() {
// dom 销毁前清除定时器
clearInterval(this.timer);
},
methods: {
// 判断飘窗显示
checkInTime() {
let date = new Date().getTime();
let startTime = new Date(this.floatWindowStartTimer).getTime(),
endTime = new Date(this.floatWindowEndTimer).getTime();
if (date >= startTime && date <= endTime) {
this.inTime = true;
}
},
// 初始化飘窗规则
init() {
// 设置最大的top和left值:根元素可视区域宽高 - 飘窗的宽高 - 边距
this.maxTop = document.documentElement.clientHeight - 150 - 20;
this.maxLeft = document.documentElement.clientWidth - 200 - 20;
// 设置 top 和 left 的初始值
this.top = 0;
this.left = 0;
// 创建定时器前清除定时器,避免类似在 onresize 中调用 init() 时,产生多个定时器
clearInterval(this.timer);
this.timer = setInterval(() => {
this.move();
}, 20);
this.onresize();
},
// 移动函数
move() {
if (this.top >= this.maxTop || this.top < 0) {
this.stepY = -this.stepY;
}
if (this.left >= this.maxLeft || this.left < 0) {
this.stepX = -this.stepX;
}
this.top += this.stepY;
this.left += this.stepX;
},
// 鼠标悬浮在飘窗时停止移动
mouseover() {
clearInterval(this.timer);
},
// 鼠标离开飘窗时恢复移动
mouseout() {
clearInterval(this.timer);
this.timer = setInterval(() => {
this.move();
}, 20);
},
// 关闭飘窗
close() {
clearInterval(this.timer);
this.show = false;
},
// 窗口大小调整时重置飘窗规则
onresize() {
const that = this;
window.onresize = function () {
that.init();
};
}
}
};
</script>
<style scoped lang="less">
.none {
display: none;
}
.box {
z-index: 2000;
background-color: #84a7e7;
background-size: contain;
width: 235px;
height: 167px;
border-radius: 5px;
position: fixed;
text-align: left;
padding: 10px;
color: #ffffff;
top: 0;
left: 0;
.closeBtn {
img {
width: 15px;
height: 15px;
}
}
.info {
width: 55px;
height: 55px;
background-color: rgba(#84a7e7, 0.4);
border-radius: 50%;
position: relative;
margin: -3px auto 0;
.info-back {
width: 43px;
height: 43px;
background-color: rgba(#84a7e7, 0.5);
border-radius: 50%;
position: absolute;
top: 5.5px;
right: 6.5px;
img {
width: 30px;
height: 30px;
top: 7px;
right: 7px;
position: absolute;
}
}
}
> span {
text-align: right;
position: absolute;
right: 10px;
top: 10px;
color: #1e87f0;
cursor: pointer;
}
> div {
margin-top: 30px;
}
.info-text {
font-size: 20px;
font-family: PingFang SC;
font-weight: 400;
color: #333333;
margin: 30px auto 0;
text-align: center;
}
}
</style>