vue3+ts生成3d标签云

598 阅读1分钟

效果图

Video_2022-09-24_105308 00_00_00-00_00_30.gif

完整代码

<template>
  <div class="cloud_wrap">
    <div class="tagcloud-all">
      <a
        v-for="(item, i) in info.tagList"
        :key="i"
        :href="item.url"
        rel="external nofollow"
        :style="'color:' + item.color + ';top: 0;left: 0;filter:none;'"
      >{{ item.name }}</a>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { nextTick, onMounted, reactive } from 'vue';

interface Info<T> {
  tagList: T[],
  config: T
}
const info: Info<any> = reactive({
  tagList: [],
  config: {
    radius: 120,
    dtr: Math.PI / 180,
    d: 300,
    mcList: [],
    active: false,
    lasta: 1,
    lastb: 1,
    distr: true,
    tspeed: 5, // 控制旋转速度
    size: 250,
    mouseX: 0,
    mouseY: 0,
    howElliptical: 1,
    oList: null,
    oA: null,
    sa: 0,
    ca: 0,
    sb: 0,
    cb: 0,
    sc: 0,
    cc: 0
  }
});

// 生成随机数
function getRandomNum() {
  return Math.floor(Math.random() * (255 + 1));
};
// 三角函数角度计算
function sineCosine (a: number, b: number, c: number) {
  info.config.sa = Math.sin(a * info.config.dtr);
  info.config.ca = Math.cos(a * info.config.dtr);
  info.config.sb = Math.sin(b * info.config.dtr);
  info.config.cb = Math.cos(b * info.config.dtr);
  info.config.sc = Math.sin(c * info.config.dtr);
  info.config.cc = Math.cos(c * info.config.dtr);
}
// 设置初始定位
function positionAll () {
  nextTick(() => {
    let phi = 0;
    let theta = 0;
    let max = info.config.mcList.length;
    let aTmp = [];
    let oFragment = document.createDocumentFragment();
    // 随机排序
    for (let i = 0; i < info.tagList.length; i++) {
      aTmp.push(info.config.oA[i]);
    }
    aTmp.sort(() => {
      return Math.random() < 0.5 ? 1 : -1;
    });
    for (let i = 0; i < aTmp.length; i++) {
      oFragment.appendChild(aTmp[i]);
    }
    info.config.oList.appendChild(oFragment);
    for (let i = 1; i < max + 1; i++) {
      if (info.config.distr) {
        phi = Math.acos(-1 + (2 * i - 1) / max);
        theta = Math.sqrt(max * Math.PI) * phi;
      } else {
        phi = Math.random() * (Math.PI);
        theta = Math.random() * (2 * Math.PI);
      }
      // 坐标变换
      info.config.mcList[i - 1].cx = info.config.radius * Math.cos(theta) * Math.sin(phi);
      info.config.mcList[i - 1].cy = info.config.radius * Math.sin(theta) * Math.sin(phi);
      info.config.mcList[i - 1].cz = info.config.radius * Math.cos(phi);
      info.config.oA[i - 1].style.left = info.config.mcList[i - 1].cx + info.config.oList.offsetWidth / 2 - info.config.mcList[i - 1].offsetWidth / 2 + 'px';
      info.config.oA[i - 1].style.top = info.config.mcList[i - 1].cy + info.config.oList.offsetHeight / 2 - info.config.mcList[i - 1].offsetHeight / 2 + 'px';
    }
  })
}
// 坐标更新 让标签动起来
function update () {
  nextTick(() => {
    let a;
    let b;
    if (info.config.active) {
      a = (-Math.min(Math.max(-info.config.mouseY, -info.config.size), info.config.size) / info.config.radius) * info.config.tspeed;
      b = (Math.min(Math.max(-info.config.mouseX, -info.config.size), info.config.size) / info.config.radius) * info.config.tspeed;
    } else {
      a = info.config.lasta * 0.98;
      b = info.config.lastb * 0.98;
    }
    info.config.lasta = a;
    info.config.lastb = b;
    if (Math.abs(a) <= 0.01 && Math.abs(b) <= 0.01) {
      return
    }
    let c = 0;
    sineCosine(a, b, c);
    for (let j = 0; j < info.config.mcList.length; j++) {
      let rx1 = info.config.mcList[j].cx;
      let ry1 = info.config.mcList[j].cy * info.config.ca + info.config.mcList[j].cz * (-info.config.sa);
      let rz1 = info.config.mcList[j].cy * info.config.sa + info.config.mcList[j].cz * info.config.ca;
      let rx2 = rx1 * info.config.cb + rz1 * info.config.sb;
      let ry2 = ry1;
      let rz2 = rx1 * (-info.config.sb) + rz1 * info.config.cb;
      let rx3 = rx2 * info.config.cc + ry2 * (-info.config.sc);
      let ry3 = rx2 * info.config.sc + ry2 * info.config.cc;
      let rz3 = rz2;
      info.config.mcList[j].cx = rx3;
      info.config.mcList[j].cy = ry3;
      info.config.mcList[j].cz = rz3;
      let per = info.config.d / (info.config.d + rz3);
      info.config.mcList[j].x = (info.config.howElliptical * rx3 * per) - (info.config.howElliptical * 2);
      info.config.mcList[j].y = ry3 * per;
      info.config.mcList[j].scale = per;
      info.config.mcList[j].alpha = per;
      info.config.mcList[j].alpha = (info.config.mcList[j].alpha - 0.6) * (10 / 6);
    }
    doPosition();
    depthSort();
  })
}
function doPosition() {
  nextTick(() => {
    let l = info.config.oList.offsetWidth / 2;
    let t = info.config.oList.offsetHeight / 2;
    for (let i = 0; i < info.config.mcList.length; i++) {
      info.config.oA[i].style.left = info.config.mcList[i].cx + l - info.config.mcList[i].offsetWidth / 2 + 'px';
      info.config.oA[i].style.top = info.config.mcList[i].cy + t - info.config.mcList[i].offsetHeight / 2 + 'px';
      info.config.oA[i].style.fontSize = Math.ceil(12 * info.config.mcList[i].scale / 2) + 8 + 'px';
      // info.config.oA[i].style.filter = "alpha(opacity=" + 100 * info.config.mcList[i].alpha + ")";
      info.config.oA[i].style.opacity = info.config.mcList[i].alpha;
    }
  })
}
function depthSort() {
  nextTick(() => {
    const aTmp = [];
    for (let i = 0; i < info.config.oA.length; i++) {
      aTmp.push(info.config.oA[i]);
    }
    aTmp.sort(function (vItem1, vItem2) {
      if (vItem1.cz > vItem2.cz) {
        return -1;
      } else if (vItem1.cz < vItem2.cz) {
        return 1;
      } else {
        return 0;
      }
    });
    for (let i = 0; i < aTmp.length; i++) {
      aTmp[i].style.zIndex = i;
    }
  })
}
// 生成随机颜色
function query(tagListOrg: any[]) {
  if (!Array.isArray(tagListOrg)) {
    return;
  }
  // 给tagList添加随机颜色
  tagListOrg.forEach((item: any) => {
    item.color = "rgb(" + getRandomNum() + "," + getRandomNum() + "," + getRandomNum() + ")";
  })
  info.tagList = tagListOrg;
  onReady();
}
// 生成标签云
function onReady() {
  nextTick(() => {
    info.config.oList = document.querySelector('.tagcloud-all');
    info.config.oA = info.config.oList.getElementsByTagName('a')
    var oTag = null;
    for (var i = 0; i < info.config.oA.length; i++) {
      const oTag: any = {};
      oTag.offsetWidth = info.config.oA[i].offsetWidth;
      oTag.offsetHeight = info.config.oA[i].offsetHeight;
      info.config.mcList.push(oTag);
    }
    sineCosine(0, 0, 0);
    positionAll();
    info.config.oList.onmouseover = () => {
      info.config.active = true;
    }
    info.config.oList.onmouseout = () => {
      info.config.active = false;
    }
    info.config.oList.onmousemove = (event: any) => {
      const oEvent: any = window.event || event;
      info.config.mouseX = oEvent.clientX - (info.config.oList.offsetLeft + info.config.oList.offsetWidth / 2);
      info.config.mouseY = oEvent.clientY - (info.config.oList.offsetTop + info.config.oList.offsetHeight / 2);
      info.config.mouseX /= 5;
      info.config.mouseY /= 5;
    }
    setInterval(() => {
      update()
    }, 30);
  })
}

onMounted(() => {
  nextTick(() => {
    const tagListOrg = [
      { name: '标签1', url: 'www.baidu.com' },
      { name: '标签2', url: 'www.baidu.com' },
      { name: '标签3', url: 'www.baidu.com' },
      { name: '标签4', url: 'www.baidu.com' },
      { name: '标签5', url: 'www.baidu.com' },
      { name: '标签6', url: 'www.baidu.com' },
      { name: '标签7', url: 'www.baidu.com' },
      { name: '标签8', url: 'www.baidu.com' },
      { name: '标签9', url: 'www.baidu.com' },
      { name: '标签10', url: 'www.baidu.com' },
      { name: '标签11', url: 'www.baidu.com' },
      { name: '标签12', url: 'www.baidu.com' },
      { name: '标签13', url: 'www.baidu.com' },
      { name: '标签14', url: 'www.baidu.com' },
      { name: '标签15', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签16', url: 'www.baidu.com' },
      { name: '标签17', url: 'www.baidu.com' }
    ];
    query(tagListOrg)
  })
})
</script>

<style lang="scss" scoped>
.cloud_wrap {
  width: 100%;
  height: 100%;
  // 标签云
  .tagcloud-all {
    height: 100%;
    position: relative;
    a {
      position: absolute;
      top: 0px;
      left: 0px;
      color: #fff;
      font-weight: bold;
      text-decoration: none;
      padding: 3px 6px;
      &:hover {
        color: #FF0000;
        letter-spacing: 2px;
      }
    }
  }
}
</style>