最近想给自己的博客上加上一个 3D 标签云的效果,用来表示自己博客文章的分组,网上找到了 canvas 实现的,还有 a 元素实现的解析 3D 标签云,我想让标签可以选择和点击,又不想在标签数量较多时操作 a 标签导致性能问题,于是 svg 就成了一个不错的选择。
这里实现的核心主要是参考了前面的那篇解析 3D 标签云的文章,作者给出了源码,讲解也比较通俗易懂。大体来说,整个代码分三步:
贴上代码:
- <div id='app'>
- <svg :width='width' :height='height' @mousemove='listener($event)'>
- <a :href="tag.href" v-for='tag in tags'>
- <text :x='tag.x' :y='tag.y' :font-size='20 * (600/(600-tag.z))' :fill-opacity='((400+tag.z)/600)'>
- {{tag.text}}
- </text>
- </a>
- </svg>
- </div>
在模板中,借用指令
来渲染标签,每个标签上绑定了 x,y,font-size(用来表现 z 轴),fill-opacity(都是与 z 坐标有关的表达式,用来表现 z 轴), 及 text;
- v-for
- data: {
- width: 700,
- //svg宽度
- height: 700,
- //svg高度
- tagsNum: 20,
- //标签数量
- RADIUS: 200,
- //球的半径
- speedX: Math.PI / 360,
- //球一帧绕x轴旋转的角度
- speedY: Math.PI / 360,
- //球-帧绕y轴旋转的角度
- tags: []
- }
- computed: {
- CX() { //球心x坐标
- return this.width / 2;
- },
- CY() { //球心y坐标
- return this.height / 2;
- }
- },
做好了上面的基础,下面我们来初始化标签数据:
- created() { //初始化标签位置
- let tags = [];
- for (let i = 0; i < this.tagsNum; i++) {
- let tag = {};
- let k = -1 + (2 * (i + 1) - 1) / this.tagsNum;
- let a = Math.acos(k);
- let b = a * Math.sqrt(this.tagsNum * Math.PI) //计算标签相对于球心的角度
- tag.text = i + 'tag';
- tag.x = this.CX + this.RADIUS * Math.sin(a) * Math.cos(b); //根据标签角度求出标签的x,y,z坐标
- tag.y = this.CY + this.RADIUS * Math.sin(a) * Math.sin(b);
- tag.z = this.RADIUS * Math.cos(a);
- tag.href = 'https://imgss.github.io'; //给标签添加链接
- tags.push(tag);
- }
- this.tags = tags; //让vue替我们完成视图更新
- },
到了这里,我们就算了算坐标,vue 完成了视图更新的工作,这时基本上就可以得到一副静态的图像了: 下面就是通过改变每一个 tag 的 x,y 的值来使球旋转起来;实现方法是 rotateX,rotateY 函数:
- rotateX(angleX) {
- var cos = Math.cos(angleX);
- var sin = Math.sin(angleX);
- for (let tag of this.tags) {
- var y1 = (tag.y - this.CY) * cos - tag.z * sin + this.CY;
- var z1 = tag.z * cos + (tag.y - this.CY) * sin;
- tag.y = y1;
- tag.z = z1;
- }
- },
- rotateY(angleY) {
- var cos = Math.cos(angleY);
- var sin = Math.sin(angleY);
- for (let tag of this.tags) {
- var x1 = (tag.x - this.CX) * cos - tag.z * sin + this.CX;
- var z1 = tag.z * cos + (tag.x - this.CX) * sin;
- tag.x = x1;
- tag.z = z1;
- }
- },
这两个函数就是根据标签原来的坐标和球旋转的角度算出新的坐标,最后在 mounted 钩子下面,写一个 animate 函数,不断调用这两个函数,实现旋转动画
- mounted() { //使球开始旋转
- setInterval(() = >{
- this.rotateX(this.speedX);
- this.rotateY(this.speedY);
- },
- 17)
- },
全部代码如下:
- <script>
- var app = new Vue({
- el: '#app',
- data: {
- width: 700,
- height: 700,
- tagsNum: 20,
- RADIUS: 200,
- speedX: Math.PI / 360,
- speedY: Math.PI / 360,
- tags: []
- },
- computed: {
- CX() {
- return this.width / 2;
- },
- CY() {
- return this.height / 2;
- }
- },
- created() { //初始化标签位置
- let tags = [];
- for (let i = 0; i < this.tagsNum; i++) {
- let tag = {};
- let k = -1 + (2 * (i + 1) - 1) / this.tagsNum;
- let a = Math.acos(k);
- let b = a * Math.sqrt(this.tagsNum * Math.PI);
- tag.text = i + 'tag';
- tag.x = this.CX + this.RADIUS * Math.sin(a) * Math.cos(b);
- tag.y = this.CY + this.RADIUS * Math.sin(a) * Math.sin(b);
- tag.z = this.RADIUS * Math.cos(a);
- tag.href = 'https://imgss.github.io';
- tags.push(tag);
- }
- this.tags = tags;
- },
- mounted() { //使球开始旋转
- setInterval(() = >{
- this.rotateX(this.speedX);
- this.rotateY(this.speedY);
- },
- 17)
- },
- methods: {
- rotateX(angleX) {
- var cos = Math.cos(angleX);
- var sin = Math.sin(angleX);
- for (let tag of this.tags) {
- var y1 = (tag.y - this.CY) * cos - tag.z * sin + this.CY;
- var z1 = tag.z * cos + (tag.y - this.CY) * sin;
- tag.y = y1;
- tag.z = z1;
- }
- },
- rotateY(angleY) {
- var cos = Math.cos(angleY);
- var sin = Math.sin(angleY);
- for (let tag of this.tags) {
- var x1 = (tag.x - this.CX) * cos - tag.z * sin + this.CX;
- var z1 = tag.z * cos + (tag.x - this.CX) * sin;
- tag.x = x1;
- tag.z = z1;
- }
- },
- listener(event) { //响应鼠标移动
- var x = event.clientX - this.CX;
- var y = event.clientY - this.CY;
- this.speedX = x * 0.0001 > 0 ? Math.min(this.RADIUS * 0.00002, x * 0.0001) : Math.max( - this.RADIUS * 0.00002, x * 0.0001);
- this.speedY = y * 0.0001 > 0 ? Math.min(this.RADIUS * 0.00002, y * 0.0001) : Math.max( - this.RADIUS * 0.00002, y * 0.0001);
- }
- }
- })
- </script>
完整 demo · vue · no vue
vue 的数据绑定可以减少我们对 dom 的操作,而将关注点放在逻辑上面,vue 构造函数提供的几个选项可以帮助我们更好的组织代码
来源: http://www.cnblogs.com/imgss/p/tagcloud.html