一、效果展示
啦啦啦~ 终于把这个散开的词云效果给搞出来了,废话不多说,上效果;
二、技术栈
vue3+vue-cli+css3
用css3 的animation动画 实现往外扩展循环播放的效果
随机生成每个元素的动画时间和延迟时间以及缩放大小
利用css3的animation-play-state: paused实现鼠标悬浮动画停止的效果
三、实现过程
mouseenter
鼠标移入添加了stop
类名停止动画,mouseleave
鼠标移出动画继续
html
<ul id="cloudCon">
<li
:style="{ animation: item.styleCon }"
v-for="(item, index) in hotWord"
:key="`${currtIndex}_${index}`"
:class="{ stop: item.isStop }"
@click="handleMes(item.name)"
@mouseenter="item.isStop = true"
@mouseleave="item.isStop = false"
>
{{ item.name }}
</li>
</ul>
js
// 热搜词
let hotWord = reactive([
{ name: "万事如意万事如意万事如意" },
{ name: "事事如意" },
{ name: "万事亨通" },
{ name: "一帆风顺" },
{ name: "万事大吉" },
{ name: "吉祥如意吉祥如意" },
{ name: "步步高升" },
{ name: "步步登高" },
{ name: "三羊开泰" },
{ name: "得心应手" },
{ name: "财源广进" },
{ name: "阖家安康阖家安康" },
{ name: "万事如意" },
{ name: "事事如意 " },
{ name: "万事亨通万事亨通" },
]);
// 随机生成的
let moveAry = reactive([]);
onMounted(() => {
setStyle();
setCla();
window.addEventListener("resize", () => {
// 监听页面尺寸变化
setStyle();
setCla();
});
});
//随机生成每个标签的移动样式并动态添加到页面中
const setStyle = () => {
let runkeyframes = "";
// 往不同的方向
let d = ["+", "-"];
// 动画范围
let w = (document.getElementById("cloudCon").offsetWidth - 100) / 2;
let h = (document.getElementById("cloudCon").offsetHeight - 40) / 2;
for (var i = 0; i < hotWord.length; i++) {
let ts = (Math.random() + 0.4).toFixed(2);
let th = d[Math.round(Math.random())]; //随机取符号
let x = Math.ceil(Math.random() * w);
let x1 = x;
x1 = th == "+" && x + 200 > w ? x - 200 : x;
let tx = th + x1;//横向平移translateX
let y = Math.ceil(Math.random() * h).toFixed(2);
let y1 = y - 87 * ts > h ? h : y;
let ty = d[Math.round(Math.random())] + y1; //纵向平移translateY
runkeyframes += ` @keyframes move${i + 1}{
0% {
opacity: 0;
transform: translateX(0) translateZ(-300px) scale(0.2);
}
25% {
opacity: 1;
}
50% {
opacity: 1;
transform: translateX(${tx}px) translateY(${ty}px) translateZ(0)
scale(${ts});
}
90% {
opacity: 1;
transform: translateX(${tx}px) translateY(${ty}px) translateZ(0)
scale(${ts});
}
100% {
opacity: 0;
transform: translateX(${tx}px) translateY(${ty}px) translateZ(0)
scale(${ts});
}
}`;
}
// 创建style标签
const style = document.createElement("style");
// 设置style属性
style.type = "text/css";
// 将 keyframes样式写入style内
style.innerHTML = runkeyframes;
// 将style样式存放到head标签
document.getElementsByTagName("head")[0].appendChild(style);
};
// 给hotWord添加styleCon
const setCla = () => {
filterItem();
hotWord.map((item,index) => {
let ts = (Math.random() * 10).toFixed(2);
item.styleCon = ` move${moveAry[index]} 10s ease-in-out infinite ${ts}s`;
});
};
//随机生成 hotWord.length个不同的随机整数,以免位置重复相互覆盖
const filterItem = () => {
var count = hotWord.length;
var originalArray = new Array();
for (var i = 0; i < count; i++) {
originalArray[i] = i + 1;
}
for (var num, i = 0; i < count; i++) {
do {
num = Math.floor(Math.random() * count);
} while (originalArray[num] == null);
moveAry.push(originalArray[num]);
originalArray[num] = null;
}
};
less
#cloudCon {
width: 100%;
height: calc(~"100% - 84px");
margin-top: 84px;
position: relative;
li {
background: rgba(255, 255, 255, 0.76) !important;
padding: 6px 9px 6px 19.5px;
color: #1135f1;
font-size: 16px;
display: inline-block;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
transform-style: preserve-3d;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 0;
&::after {
content: "";
width: 6px;
height: 6px;
background: #fea41d;
position: absolute;
left: 9px;
top: 12px;
}
&.stop {
animation-play-state: paused !important; /* 暂停动画 */
}
}
}
}