首页 > 代码库 > rotate 3d II

rotate 3d II

  第二篇来学习如何用canvas打造3d标签云。

  demo -> 3d标签云

  参考资料:

  • 解析3D标签云,其实很简单
  • rotate 3d I

前面我们已经构造了一个三维空间旋转的模板(其实Ball类初始化时有bug...),如何构造一个标签云?思考ing...

其实就是构造一个球体,标签放在球体上,然后每个标签旋转即可。也就是说,我们可以把每个粒子当做是一个标签。

怎样构造球体?前面一篇中我用确定z参数,然后枚举x获取y的方法获得球面坐标,其实有更好的方法:

技术分享

不懂数学,试了下觉得角度的取值有两种方法:

  1. (0 <= θ <= PI && 0 <= Φ <= 2 * PI)
  2. (0 <= θ <= 2 * PI && 0 <= Φ <= PI)

可以验证x*x+y*y+z*z确实等于R*R。

有了公式,我们可以枚举角度获得坐标。

如果有n个点,我需要平均分配在球面上,怎么做?我们引入第二个公式:

技术分享

var all = 100;for(var i = 1; i <= all; i++) {  var a1 = Math.acos(1 - (2 * i) / all);  var a2 = a1 * Math.sqrt(all * Math.PI);  var x = 150 * Math.sin(a1) * Math.cos(a2);  var y = 150 * Math.sin(a1) * Math.sin(a2);  var z = 150 * Math.cos(a1);  garden.createBall(x, y, z);}

  然后就差不多了,以前的demo我都是代码自己控制旋转角度,加个事件的监听:

document.addEventListener("mousemove" , function(event){  var x = event.clientX - garden.vpx;  var y = event.clientY - garden.vpy;  garden.angleY = -x * 0.0001;  garden.angleX = y * 0.0001;});

  不考虑兼容...仅在chrome下测试。然后每帧绘制的时候,前面的demo是绘制小球,现在就是fillText,根据scale改变text的大小、透明度等。其实应该是个“标签”,应该有点击跳转的功能,无奈我的css能力为0...

完整代码:

<!DOCTYPE html><html>  <head>    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />    <title> rotate 3d</title>    <script>      window.onload = function() {        var canvas = document.getElementById(‘canvas‘);        var ctx = canvas.getContext(‘2d‘);        var garden = new Garden(canvas);        // x=r*sinθ*cosΦ   y=r*sinθ*sinΦ   z=r*cosθ;        // θ = arccos((2*num-1)/all - 1);        // Φ = θ*sqrt(all * π);        // for(var i = 0; i <= 180; i += 10)        //   for(var j = 0; j <= 360; j += 10) {        //     var a1 = Math.PI / 180 * i;        //     var a2 = Math.PI / 180 * j;        //     var x = 150 * Math.sin(a1) * Math.cos(a2);        //     var y = 150 * Math.sin(a1) * Math.sin(a2);        //     var z = 150 * Math.cos(a1);        //     garden.createBall(x, y, z);        //   }                  var all = 30;        for(var i = 1; i <= all; i++) {          var a1 = Math.acos(1- (2 * i) / all);          var a2 = a1 * Math.sqrt(all * Math.PI);          var x = 150 * Math.sin(a1) * Math.cos(a2);          var y = 150 * Math.sin(a1) * Math.sin(a2);          var z = 150 * Math.cos(a1);          garden.createBall(x, y, z);        }        document.addEventListener("mousemove" , function(event){          var x = event.clientX - garden.vpx;          var y = event.clientY - garden.vpy;                garden.angleY = -x * 0.0001;          garden.angleX = y * 0.0001;        });        setInterval(function() {garden.render();}, 1000/60);       };      function Garden(canvas, vpx, vpy) {        this.canvas = canvas;        this.ctx = this.canvas.getContext(‘2d‘);        // 三维系在二维上的原点        this.vpx = vpx === undefined? 500: vpx;        this.vpy = vpy === undefined? 250: vpy;        this.balls = [];        this.angleY = 0;        this.angleX = 0;      }      Garden.prototype = {        createBall: function(x, y, z) {          this.balls.push(new Ball(this, x, y, z));        },        render: function() {          this.ctx.clearRect(0,0,1000,500)          this.balls.sort(function (a, b) {return b.z-a.z })          for(var i = 0; i < this.balls.length; i++) {            this.balls[i].rotateY();            this.balls[i].rotateX();            this.balls[i].draw();          }        }      };      function Ball(garden, x, y, z, angleX, angleY, ballR) {        this.garden = garden;        // 三维下坐标        this.x = x === undefined? Math.random() * 200 - 100: x;        this.y = y === undefined? Math.random() * 200 - 100: y;        this.z = z === undefined? Math.random() * 200 - 100: z;        this.r = Math.floor(Math.random() * 255);        this.g = Math.floor(Math.random() * 255);        this.b = Math.floor(Math.random() * 255);        this.fontSize = (10 + 10 * Math.random());        // this.angleX = 0;        // this.angleX = angleX || Math.PI / 200;        // this.angleY = angleY === undefined? Math.PI / 100: angleY;        // 三维上半径        this.ballR = 1;        // 二维上半径        this.radius = undefined;        // 二维上坐标        this.x2 = undefined;        this.y2 = undefined;      }            Ball.prototype = {        // 绕y轴变化,得出新的x,z坐标        rotateY: function() {          var cosy = Math.cos(this.garden.angleY);          var siny = Math.sin(this.garden.angleY);          var x1 = this.z * siny + this.x * cosy;          var z1 = this.z * cosy - this.x * siny;          this.x = x1;          this.z = z1;        },        // 绕x轴变化,得出新的y,z坐标        rotateX: function() {          var cosx = Math.cos(this.garden.angleX);          var sinx = Math.sin(this.garden.angleX);          var y1 = this.y * cosx - this.z * sinx;          var z1 = this.y * sinx + this.z * cosx;          this.y = y1;          this.z = z1;        },        draw: function() {          // focalLength 表示当前焦距,一般可设为一个常量          var focalLength = 300;                    // 把z方向扁平化          var scale = focalLength / (focalLength + this.z);          this.x2 = this.garden.vpx + this.x * scale;          this.y2 = this.garden.vpy + this.y * scale;          this.radius = this.ballR * scale;          this.garden.ctx.beginPath();          this.garden.ctx.fillStyle = ‘rgba(‘+this.r+‘,‘+this.g+‘,‘+this.b+‘,‘+ Math.min(1, scale)+‘)‘;          // this.garden.ctx.arc(this.x2, this.y2, this.radius, 0, Math.PI * 2 , true);          this.garden.ctx.font = ‘bold ‘ + this.fontSize * scale+ ‘px serif‘;          this.garden.ctx.textAlign = "left";          this.garden.ctx.textBaseline = "top";          this.garden.ctx.fillText(‘博客园‘, this.x2, this.y2);          this.garden.ctx.fill();        }      }    </script>  </head>  <body bgcolor=‘#000‘>     <canvas id=‘canvas‘ width=1000 height=500 style=‘background-color:rgb(0,0,0)‘>      This browser does not support html5.    </canvas>  </body></html>

  其实用封装好的3d标签云插件的实现也大同小异,无非是用div代替canvas,然后设置原点为div的中心,原html里写好链接,然后监听,实现a标签的位置变化...不会css是硬伤...

rotate 3d II