百度前端技术学院是一个为大学生创办的免费的前端技术实践、分享、交流平台。由百度校园招聘组、百度校园品牌部、百度前端技术部以及多个百度的前端团队联合创办。学院组织了一批百度在职工程师,精心编写了数十个实践编码任务,将技术知识点系统有机地串联在各个充满趣味与挑战的任务中,同学们通过实际地编码练习来掌握知识,再辅以互相评价、学习笔记等方式,加深对于学习内容的理解。在过去的三年中,百度前端技术学院累积吸引了上万名同学参加,并且有数十名同学在学习后,顺利加入了百度,成为了百度的前端工程师。

3D标签云原理简析

作者Alvin课程标签云3107次浏览72017-03-05 17:34

开始之前

文章开始之前,应该了解几个重要的公式。回忆一下我们逝去的高中:

  • 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 * π)的取值就不是很懂了,还望知情的大神告知。

具体的代码如下:

分配坐标:

var 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度跟没旋转的效果是一样的,所以我们应该合理的设置单位时间和每一次旋转的角度值,让我们的眼睛知道它是个球,它在转!!!

代码

预览效果: http://alvin-liu.github.io/FrontCode/src/tagcloud/

代码地址: https://github.com/Alvin-Liu/FrontCode/blob/gh-pages/src/tagcloud/js/tagcloud.js

参考文章:

解析3D标签云,其实很简单

1条评论