vue词云组件

无需引入第三方库,

原文地址:sample-reels/index.vue at master · zhaowhY/sample-reels · GitHub

基于上面源码做了修改,可直接复制到组件中使用。

具体代码:

<template>
  <div>
    <div
      class="wordCloud__tagBall"
      :style="{width:`${this.width}px`,height:`${this.height}px`}"
      @mouseenter="stop"
      @mouseleave="start"
    >
      <span
        class="wordCloud__tag"
        v-for="(item, index) of data"
        :key="index"
        :style="{color:color[index % color.length],...contentEle[index].style}"
        :title="item.name+item.value"
      >{{item.name}}</span>
    </div>
  </div>
</template>


<script>
export default {
  name: 'cloudWork',
  props: {
    width: {
      type: Number,
      default: 300
    },
    height: {
      type: Number,
      default: 300
    },
    // 测试数据
    data: {
      type: Array,
      default: () => [
        {
          name: '安氏一类',
          value: 30
        },
        {
          name: '安氏二类',
          value: 30
        },
        {
          name: '安氏三类',
          value: 30
        },
        {
          name: '安氏四类',
          value: 30
        },
        {
          name: '安氏五类',
          value: 30
        },
        {
          name: '安氏一类',
          value: 30
        },
        {
          name: '安氏二类',
          value: 30
        },
        {
          name: '安氏三类',
          value: 30
        },
        {
          name: '安氏四类',
          value: 30
        },
        {
          name: '安氏五类',
          value: 30
        },
        {
          name: '安氏一类',
          value: 30
        },
        {
          name: '安氏二类',
          value: 30
        },
        {
          name: '安氏三类',
          value: 30
        },
        {
          name: '安氏四类',
          value: 30
        },
        {
          name: '安氏五类',
          value: 30
        }
      ]
    }
  },
  data: () => ({
    color: ['#2D4DB6', '#04B67C', '#D1AF07', '#E27914', '#CB4A4D', '#B02690'],
    contentEle: [],
    direction: '-1',
    speed: 400,
    animateID: null
  }),
  created() {
    this.contentEle = this.data.map(() => ({
      x: 0,
      y: 0,
      z: 0,
      style: {}
    }));
  },
  mounted() {
    this.innit();
  },
  methods: {
    innit() {
      const RADIUSX = (this.width - 50) / 2;
      const RADIUSY = (this.height - 50) / 2;
      this.contentEle = [];
      for (let i = 0; i < this.data.length; i += 1) {
        const k = -1 + (2 * (i + 1) - 1) / this.data.length;
        const a = Math.acos(k);
        const b = a * Math.sqrt(this.data.length * Math.PI);
        const x = RADIUSX * Math.sin(a) * Math.cos(b);
        const y = RADIUSY * Math.sin(a) * Math.sin(b);
        const z = RADIUSX * Math.cos(a);
        const singleEle = {
          x,
          y,
          z,
          style: {}
        };
        this.contentEle.push(singleEle);
      }
      this.animate();
    },
    animate() {
      this.rotateX();
      this.rotateY();
      this.move();
      this.animateID = window.requestAnimationFrame(this.animate);
    },
    rotateX() {
      const angleX = ['-1', '1'].includes(this.direction)
        ? Math.PI / Infinity
        : Math.PI / ((Number(this.direction) / 2) * Number(this.speed));
      const cos = Math.cos(angleX);
      const sin = Math.sin(angleX);

      this.contentEle = this.contentEle.map((t) => {
        const y1 = t.y * cos - t.z * sin;
        const z1 = t.z * cos + t.y * sin;
        return {
          ...t,
          y: y1,
          z: z1
        };
      });
    },
    rotateY() {
      const angleY = ['-2', '2'].includes(this.direction)
        ? Math.PI / Infinity
        : Math.PI / (Number(this.direction) * Number(this.speed));
      const cos = Math.cos(angleY);
      const sin = Math.sin(angleY);
      this.contentEle = this.contentEle.map((t) => {
        const x1 = t.x * cos - t.z * sin;
        const z1 = t.z * cos + t.x * sin;
        return {
          ...t,
          x: x1,
          z: z1
        };
      });
    },
    move() {
      const CX = this.width / 2;
      const CY = this.height / 2;
      this.contentEle = this.contentEle.map((singleEle) => {
        const { x, y, z } = singleEle;
        const fallLength = 500;
        const RADIUS = (this.width - 50) / 2;
        const scale = fallLength / (fallLength - z);
        const alpha = (z + RADIUS) / (2 * RADIUS);
        const left = `${x + CX - 15}px`;
        const top = `${y + CY - 15}px`;
        const transform = `translate(${left}, ${top}) scale(${scale})`;
        const style = {
          ...singleEle.style,
          opacity: alpha + 0.5,
          zIndex: parseInt(scale * 100, 10),
          transform
        };
        return {
          x,
          y,
          z,
          style
        };
      });
    },
    // 鼠标移入暂停
    stop() {
      window.cancelAnimationFrame(this.animateID);
    },
    // 鼠标离开恢复
    start() {
      this.animate();
    }
  }
};
</script>


<style  scoped>
button {
  margin: 20px;
}
.wordCloud__tagBall {
  margin: 50px auto;
  position: relative;
}

.wordCloud__tag {
  display: block;
  position: absolute;
  left: 0px;
  top: 0px;
  color: green;
  text-decoration: none;
  font-size: 15px;
  font-family: '微软雅黑';
  font-weight: bold;
}
.wordCloud__tag :hover {
  color: red;
}

.wordCloud__home {
  display: flex;
  justify-content: center;
}
</style>

 上面是网上找的词云组件,原文地址,但是自己项目是vue3 + tsx写的,所以稍微改了下写法,具体如下:

import { defineComponent, reactive, onMounted } from 'vue';

import classes from './index.module.less'

export default defineComponent({
  name: 'WordCloud',
  props: {
    data: {
      type: Array,
      defualt: () => []
    }
  },
  setup(props, {emit}) {
    const state = reactive({
      width: 0,
      height: 0,
      color: ['#2D4DB6', '#04B67C', '#D1AF07', '#E27914', '#CB4A4D', '#B02690'],
      contentEle: [] as any,
      direction: '-1',
      speed: 400,
      animateID: null as any
    })

    state.contentEle = props.data && props.data.map(() => ({
      x: 0,
      y: 0,
      z: 0,
      style: {}
    }));

    onMounted(() => {
      const dom = document.getElementById('wordCloud')
      state.width = (dom && dom.clientWidth) as number
      state.height = (dom && dom.clientHeight) as number
      init()
    })
    
    const init = () => {
      const RADIUSX = (state.width - 50) / 2;
      const RADIUSY = (state.height - 50) / 2;
      state.contentEle = [];
      if(props.data && props.data.length)
      for (let i = 0; i < props.data.length; i += 1) {
        const k = -1 + (2 * (i + 1) - 1) / props.data.length;
        const a = Math.acos(k);
        const b = a * Math.sqrt(props.data.length * Math.PI);
        const x = RADIUSX * Math.sin(a) * Math.cos(b);
        const y = RADIUSY * Math.sin(a) * Math.sin(b);
        const z = RADIUSX * Math.cos(a);
        const singleEle = {
          x,
          y,
          z,
          style: {}
        };
        state.contentEle.push(singleEle);
      }
      animate();
    }

    const animate = () => {
      rotateX();
      rotateY();
      move();
      state.animateID = window.requestAnimationFrame(animate);
    }

    const rotateX = () => {
      const angleX = ['-1', '1'].includes(state.direction)
        ? Math.PI / Infinity
        : Math.PI / ((Number(state.direction) / 2) * Number(state.speed));
      const cos = Math.cos(angleX);
      const sin = Math.sin(angleX);

      state.contentEle = state.contentEle.map((t:any) => {
        const y1 = t.y * cos - t.z * sin;
        const z1 = t.z * cos + t.y * sin;
        return {
          ...t,
          y: y1,
          z: z1
        };
      });
    }

    const rotateY = () => {
      const angleY = ['-2', '2'].includes(state.direction)
        ? Math.PI / Infinity
        : Math.PI / (Number(state.direction) * Number(state.speed));
      const cos = Math.cos(angleY);
      const sin = Math.sin(angleY);
      state.contentEle = state.contentEle.map((t: any) => {
        const x1 = t.x * cos - t.z * sin;
        const z1 = t.z * cos + t.x * sin;
        return {
          ...t,
          x: x1,
          z: z1
        };
      });
    }
    const move = () => {
      const CX = state.width / 2;
      const CY = state.height / 2;
      state.contentEle = state.contentEle.map((singleEle:any) => {
        const { x, y, z } = singleEle;
        const fallLength = 500;
        const RADIUS = (state.width - 50) / 2;
        const scale = fallLength / (fallLength - z);
        const alpha = (z + RADIUS) / (2 * RADIUS);
        const left = `${x + CX - 15}px`;
        const top = `${y + CY - 15}px`;
        const transform = `translate(${left}, ${top}) scale(${scale})`;
        const style = {
          ...singleEle.style,
          opacity: alpha + 0.5,
          zIndex: parseInt((scale * 100).toString(), 10),
          transform
        };
        return {
          x,
          y,
          z,
          style
        };
      });
    }

    // 鼠标移入暂停
    const stop = () => {
      window.cancelAnimationFrame(state.animateID);
    }
    // 鼠标离开恢复
    const start = ()=>  {
      animate();
    }

    const wordClick = (item:any) => {
      emit('wordClick', item)
    }

    return () => {
      return (
        <div
          id="wordCloud"
          class={classes.wordCloud__tagBall}
          onMouseenter={stop}
          onMouseleave={start}
        >
          {
            props.data && props.data.map((item: any, index:number)=> {
              return (
                <span
                  class={classes.wordCloud__tag}
                  key={index}
                  style={{color: state.color[index % state.color.length],...state.contentEle[index].style}}
                  title={item.name}
                  onClick={() => wordClick(item)}
                >{item.name}</span>
              )
            })
          }
        </div>
      )
    }
  }
})

// index.module.less
.wordCloud__tagBall {
  position: relative;
  width: 100%;
  height: 100%;
}

.wordCloud__tag {
  display: block;
  position: absolute;
  left: 0px;
  top: 0px;
  color: green;
  text-decoration: none;
  font-size: 15px;
  font-family: '微软雅黑';
  font-weight: bold;
  cursor: pointer;
}
.wordCloud__tag:hover {
  color: red;
}

.wordCloud__home {
  display: flex;
  justify-content: center;
}

效果:

 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值