vue3+css3 实现 一个不一样的词云效果

一、效果展示

啦啦啦~ 终于把这个散开的词云效果给搞出来了,废话不多说,上效果;

二、技术栈

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; /* 暂停动画 */
      }
    }
  }
}
相关推荐
leoZ2319 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
荒诞英雄13 小时前
从 17MB Vendor 大包到按需加载:Vite 多入口 Vue 组件库首屏优化实践
vue.js·vite
jsonbro13 小时前
手把手带你实现发布订阅模式
前端·vue.js·设计模式
Cobyte15 小时前
23.Vue Vapor 组件 attrs 的实现
前端·javascript·vue.js
斯特凡今天也很帅16 小时前
xml页面可以打开,不报错,但是显示数据的地方也不显示,我是怎么解决的
xml·vue.js
柯克七七18 小时前
给老项目加了 TypeScript,本来只想自己爽,结果全公司代码审查标准被我抬高了
前端·vue.js·typescript
自然 醒19 小时前
前端如何实现在线预览office常见文件功能?
前端·vue.js
肉肉不吃 肉20 小时前
前端调试跨域如何解决
前端·vue.js
卓怡学长20 小时前
w268基于springboot + vue 物流系统
java·数据库·vue.js·spring boot·spring·intellij-idea
2601_9628517421 小时前
计算机毕业设计之基于SSM的网络安全课程资源分享平台
vue.js·算法·安全·web安全·yolo·目标检测·课程设计