3D标签云
Closed this issue · 0 comments
开始之前
文章开始之前,应该了解几个重要的公式。回忆一下我们逝去的高中:
- sinθ、cosθ的值得大小区间是[-1,1];
- 弧度跟角度的转换公式是:弧度 = π/180 * 角度。
1、在空间直角坐标系中,以坐标原点为球心,半径为R的球面的参数方程为:
x = r * sinθ * cosΦ;
y = r * sinθ * sinΦ;
z = r * cosθ;
说明:θ为点跟圆心的连线与z轴的夹角,Φ是点跟圆心连线在xy平面投影线与x轴的夹角(可以根据需要建立不同的坐标系,或者取不同的夹角,坐标的表达方式可以有很多,但原理是类似的)。
资料:http://baike.baidu.com/item/%E7%90%83%E9%9D%A2?fr=aladdin
2、旋转公式:
x1 = cosθ * x - sinθ * y;
y1 = cosθ * y + sinθ * x;
说明:(x,y)是开始的坐标,θ是旋转的角度,(x1,y1)是结束的坐标。球绕某一条轴的旋转可以抽象成圆绕圆心旋转,根据旋转前的坐标和角度可以求出旋转后的坐标。
资料:http://www.cnblogs.com/ywxgod/archive/2010/08/06/1793609.html
正文部分
了解了几个公式之后,3D标签云的旋转其实就很简单了。原理就是把标签当成一个点,通过设置不同的θ,Φ把它们平均分布在球面的各个坐标点上。 旋转x轴或者y轴达到球体旋转的目的。z轴是一条虚拟出来的轴,它与我们的屏幕垂直。我们不能真的实现一个立体的球体出来,但是我们可以通过"近大远小"达到视觉的欺骗,呈现一种立体的感觉。
原理差不多说完了,接下来开始具体的代码实现过程。
设置坐标
设置坐标是最重要,也是相对难的一步,因为我们要达到平均分布,避免分布太过集中或者重叠。因为半径是固定的,所以我们从角度出发,调整角度达到平均分布的目的。接下来我们引入一位大神的式子,我也不知道出处是那里,但是确实很好用,式子如下:
θ = arccos(((2 * i) - 1) / len - 1);
Φ = θ * sqrt(len * π);
第一个式子arccos
中的((2 * i) - 1) / len - 1
其实是一个[-1,1]区间上关于0对称分布的等差数列,通过反余弦转换成弧度值,的确是一个很高明的式子,学渣的我确实想不出来。第二个式子,是关于θ的等差(变量只有θ),不过参数sqrt(len * π)
的取值就不是很懂了,还望知情的大神告知。
具体的代码如下:
分配坐标:
const init = function() {
const tagEle = cloud.querySelectorAll('.tag'),
tagLen = tagEle.length
for (let i = 0; i < tagLen; i++) {
// 设置随机坐标,平均分布
let a = Math.acos((2 * (i + 1) - 1) / tagLen - 1), // θ = arccos(((2*(i+1))-1)/len - 1)
b = a * Math.sqrt(tagLen * Math.PI), // Φ = θ*sqrt(all * π)
x = R * Math.sin(a) * Math.cos(b), // x轴坐标: x=r*sinθ*cosΦ
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() // 初始化位置
}
animate() // 旋转
}
设置坐标及参数:
let scale = _focalLength / (_focalLength - this.z),
alpha = (this.z + R) / (2 * R),
ele = this.ele
ele.style.fontSize = 14 * 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 + cloud.offsetWidth / 2 - ele.offsetWidth / 2 + 'px'
ele.style.top = this.y + cloud.offsetHeight / 2 - ele.offsetHeight / 2 + 'px'
scale、alpha 都是取关于z坐标的递增函数,所以可以根据需要调整函数达到更好的显示效果。
旋转
开始的时候我们简单介绍了圆的旋转,球的旋转其实类似圆。例如绕z轴旋转,其实改变的是x,y坐标的值,z坐标的值并没有变化。理解了这一层,我们可以得出绕x轴旋转的和y轴旋转的函数,分别为:
/*
绕x轴旋转
y = ycosθ - zsinθ;
z = ysinθ + zcosθ;
*/
function rotateX () {
let cos = Math.cos(angleX),
sin = Math.sin(angleX)
tags.forEach(function (tag) {
let y = tag.y * cos - tag.z * sin,
z = tag.z * cos + tag.y * sin
tag.y = y
tag.z = z
})
};
/*
绕y轴旋转
x = xcosθ - zsinθ;
z = xsinθ + zcosθ;
*/
function rotateY () {
let cos = Math.cos(angleY),
sin = Math.sin(angleY)
tags.forEach(function (tag) {
let x = tag.x * cos - tag.z * sin,
z = tag.z * cos + tag.x * sin
tag.x = x
tag.z = z
})
}
于是我们就能通过控制angleX、angleY的大小来达到旋转的目的了,值越大,单位时间旋转的角度越大,也就是旋转的速度越快。当然,旋转360度跟没旋转的效果是一样的,所以我们应该合理的设置单位时间和每一次旋转的角度值,让我们的眼睛知道它是个球,它在转!!!
结束
以上内容来自一个前端低手的个人总结与整理,不足之处,还请指正。
题外:通过这次的练习才知道,学好数学是多么重要啊!!!
革命尚未成功,同志还需很努力!!!