无需引入第三方库,
原文地址: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;
}
效果: