vivian blog

3dtagCloud

前言

最近准备要找实习了,然后就想着要做个漂漂亮亮的简历。想起ife项目中的标签云,于是便准备做一个3d的标签云效果


总体思路

  • 为所有标签要随机生成坐标,平均分布在球面上,然后再根据旋转公式,获取旋转后的坐标,然后再进行动画。以下是具体做法

重要概念

  • sinθ与cosθ的值域为[-1,1](很重要)
  • 球坐标公式,以坐标原点为中心,半径为R的球面的参数方程为
1
2
3
x = r * sinθ * cosΦ;
y = r * sinθ * sinΦ;
z = r * cosθ;

原理
资料:球坐标公式推导

  • 旋转公式
1
2
c = cosβ * x - sinβ * y;//(x,y,z)为旋转前的坐标,(c,d,z1)为旋转后的坐标
d = cosβ * y - sinβ * x;

原理
球绕某一轴旋转可以抽象成圆绕圆心旋转!
资料:坐标旋转公式推导

设置坐标

设置坐标是相对较难的一步,因为我们要将标签平均分布在球面上,藉此,引用一下大神的式子

1
2
θ = arccos(((2 * i) - 1) / len - 1);
Φ = θ * sqrt(len * π);

如图

第一个式子arccos中的(2 * i) - 1) / len - 1实际上是一个在[-1,1]区间中关于0对称的等差数列(因为sinθ,cosθ的值域是[-1,1]),第二个式子中的sqrt(len * π)却不是很懂其中的原理

详细代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
for (let i = 0; i < tagLen; i++) {
// 设置随机坐标,平均分布
let a = Math.acos((2 * (i + 1) - 1) / tagLen - 1), // θ = arccos(((2*(i+1))-1)/len - 1),基于[-1,1]的关于0对称的等差数列
b = a * Math.sqrt(tagLen * Math.PI), // Φ = θ*sqrt(all * π),不懂原理
x = R * Math.sin(a) * Math.cos(b), // x轴坐标: x=r*sinθ*cosΦ,详情请参考https://zh.wikipedia.org/wiki/%E7%90%83%E5%BA%A7%E6%A8%99%E7%B3%BB
y = R * Math.sin(a) * Math.sin(b), // y轴坐标: x=r*sinθ*cosΦ
z = R * Math.cos(a), // z轴坐标: z=r*cosθ
t = new tag(tagEle[i], x, y, z);
tagEle[i].style.color = '#' + Math.floor(Math.random() * 0xffffff).toString(16); // 设置随机颜色
tags.push(t);
t.move(); // 初始化位置
}

旋转

先前已经说明了思路,现在就直接post上代码吧

1
2
3
4
5
6
7
8
9
10
function rotateX() {
let cos = Math.cos(angleX),
sin = Math.sin(angleX);
for(var i = 0;i<tags.length;i++){
let y = tags[i].y * cos - tags[i].z * sin,
z = tags[i].z * cos + tags[i].y * sin;
tags[i].y = y;
tags[i].z = z;
}
};

其中angleX和angleY是旋转角速度的,可按需求设置,注意 在单位时间内v=w*r,且旋转360度跟没旋转的效果一样。

移动

1
2
3
4
5
6
7
8
9
10
11
12
13
tag.prototype = {
move: function() {
let scale = length / (length - this.z),
alpha = (this.z + R) / (2 * R),
ele = this.ele;
ele.style.fontSize = 15 * scale + "px";
ele.style.opacity = alpha + 0.5;
ele.style.zIndex = parseInt(scale * 100);
// 原点是 (cloud.offsetWidth/2, cloud.offsetHeight/2)
ele.style.left = this.x + board.offsetWidth / 2 - ele.offsetWidth / 2 + "px";
ele.style.top = this.y + board.offsetHeight / 2 - ele.offsetHeight / 2 + "px";
}
};

scale,alpha都是取关于z坐标递增的函数,用于设置font-sizeopacityz-index为了能够更好地形成视觉差(3d效果),可按需设置!

预览

参考链接 3dtagCloud