绘制雷达圆动画效果

相关知识

随着浏览器和手机等各种硬件设备性能的提高,AR、VR的火爆,3D技术逐渐被人们所广泛使用。由于从WebGL底层直接开发三维应用太过复杂,各种基于WebGL,封装了底层的图形接口,使得程序员能够在无需掌握繁冗的图形学知识的情况下,也能用简单的代码实现三维场景的渲染。因此,各种三维图形库开始受到开发者越来越多的关注。这其中,Three.js是的主要的一个3D javascript库。

实现效果

本次要实现的效果,就是基于Three.js引擎,实现一个雷达效果的动态圆插件,效果如下图所示:

插件效果示例

实现思路

从动画中可以看出,最底层是有一个无变化的基础圆,上层有两个半径不断增大,透明度不断变小的动态圆构成的动画。由此我们首先需要创建一个不变的圆:

1
2
3
4
5
6
7
8
9
10
11
//创建Circle Geometry
var circleBufferGeometry = new THREE.CircleBufferGeometry( this.radius, this.segments );

//创建材质
var materialMain = new THREE.MeshBasicMaterial( { color: color, opacity: 0.3, transparent: true, side: THREE.DoubleSide} );

//由材质和Geometry创建Mesh
this._circleMain = new THREE.Mesh( circleBufferGeometry, materialMain );

//将Mesh加入到场景中
this.scene.add( this._circleMain );

同上,我们还需要创建两个动态变化的圆,初始时圆时透明不可见的,执行动画之时才显示出来。由可以看出,两个圆的初始半径需要错开,最后才能产生连续的雷达扩散效果。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//创建第一个动态Circle Geometry
var circleRingOneGeometry = new THREE.CircleBufferGeometry( 0.001, segments );
//创建第二个动态Circle Geometry
var circleRingTwoGeometry = new THREE.CircleBufferGeometry( radius/2, segments );
//设置两个Circle的材质
var materialRingOne = new THREE.MeshBasicMaterial( { color: color, opacity: 0, transparent: true, side: THREE.DoubleSide} );
var materialRingTwo = new THREE.MeshBasicMaterial( { color: color, opacity: 0, transparent: true, side: THREE.DoubleSide} );
//设置两个Circle的材质可变化
materialRingOne.needsUpdate=true;
materialRingTwo.needsUpdate=true;
//创建第一个动态圆并设置透明度不可见
_circleRingOne = new THREE.Mesh( circleRingOneGeometry, materialRingOne );
_circleRingOne.material.opacity = 0;
//创建二个动态圆并设置透明度不可见
_circleRingTwo = new THREE.Mesh( circleRingTwoGeometry, materialRingTwo );
_circleRingTwo.material.opacity = 0;
//将两个动态圆加入到场景中
scene.add( _circleRingOne );
scene.add( _circleRingTwo );

编写动态变化每一帧方法。动态圆的半径与其透明度成反比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//如果当前动态圆的半径小于底层圆半径,则半径增加,透明度减少
if(_circleRingOne.geometry.boundingSphere.radius<radius){
var radiusOne = _circleRingOne.geometry.boundingSphere.radius + speed;
_circleRingOne.geometry = new THREE.CircleBufferGeometry( radiusOne, segments );
_circleRingOne.material.opacity = 1-radiusOne/radius;
}//如果当前动态圆的半径大于等于底层圆半径,则半径变为最小,透明度变为最大
else if(_circleRingOne.geometry.boundingSphere.radius>=radius){
_circleRingOne.geometry = new THREE.CircleBufferGeometry( 0.01, segments );
_circleRingOne.material.opacity = 1;
}//同动态圆一
if(_circleRingTwo.geometry.boundingSphere.radius<that.radius){
var radiusTwo = _circleRingTwo.geometry.boundingSphere.radius + speed;
_circleRingTwo.geometry = new THREE.CircleBufferGeometry( radiusTwo, segments );
_circleRingTwo.material.opacity = 1-radiusTwo/radius;
}//同动态圆一
else if(_circleRingTwo.geometry.boundingSphere.radius>=radius){
_circleRingTwo.geometry = new THREE.CircleBufferGeometry( 0.01, segments );
_circleRingTwo.material.opacity = 1;
}

最后,将功能对象化、插件化。同时增加相应控制方法,如设置位置、角度、半径、以及节点数。最后代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
* @Author: funcgis
* @Date: 2018-06-10 15:22:24
* @Last Modified by: funcgis
* @Last Modified time: 2018-06-10 16:48:51
* Three.js Animation Circle Plugin
*/

/**
* Animation Circle Object
* @param {*} scene Three.js scene Object
* @param {*} position Circle Animation's Position
* @param {*} rotation Circle Animation's Rotation
* @param {*} radius Circle's Radius
* @param {*} segments Circle's segments
* @param {*} color Circle's Color
* @param {*} speed Circle Animation's Speed
*/
var AnimationCircle = function(scene, renderer, camera, position, rotation, radius, segments, color, speed){
//properties
this.scene = scene;
this.renderer = renderer;
this.camera = camera;
this.radius = radius;
this.segments = segments;
this.color = color;
this.speed = speed;
this.position = position;
this.rotation = rotation;

this._ifAnimate = false;
this._circleMain = null;
this._circleRingOne = null;
this._circleRingTwo = null;

//init Circle Animation Objects
this._init = function(){
var circleBufferGeometry = new THREE.CircleBufferGeometry( this.radius, this.segments );
var circleRingOneGeometry = new THREE.CircleBufferGeometry( 0.001, this.segments );
var circleRingTwoGeometry = new THREE.CircleBufferGeometry( this.radius/2, this.segments );
var materialMain = new THREE.MeshBasicMaterial( { color: color, opacity: 0.3, transparent: true, side: THREE.DoubleSide} );
var materialRingOne = new THREE.MeshBasicMaterial( { color: color, opacity: 0, transparent: true, side: THREE.DoubleSide} );
var materialRingTwo = new THREE.MeshBasicMaterial( { color: color, opacity: 0, transparent: true, side: THREE.DoubleSide} );
materialMain.needsUpdate=true;
materialRingOne.needsUpdate=true;
materialRingTwo.needsUpdate=true;

this._circleMain = new THREE.Mesh( circleBufferGeometry, materialMain );

this._circleRingOne = new THREE.Mesh( circleRingOneGeometry, materialRingOne );
this._circleRingOne.material.opacity = 0;

this._circleRingTwo = new THREE.Mesh( circleRingTwoGeometry, materialRingTwo );
this._circleRingTwo.material.opacity = 0;

this.scene.add( this._circleMain );
this.scene.add( this._circleRingOne );
this.scene.add( this._circleRingTwo );
};

var that = this;
//Circle Animation function
this._animate = function(){
requestAnimationFrame( that._animate );
//do animate
if(that._ifAnimate){
if(that._circleRingOne.geometry.boundingSphere.radius<that.radius){
var radiusOne = that._circleRingOne.geometry.boundingSphere.radius + speed;
that._circleRingOne.geometry = new THREE.CircleBufferGeometry( radiusOne, that.segments );
that._circleRingOne.material.opacity = 1-radiusOne/that.radius;
}
else if(that._circleRingOne.geometry.boundingSphere.radius>=that.radius){
that._circleRingOne.geometry = new THREE.CircleBufferGeometry( 0.01, that.segments );
that._circleRingOne.material.opacity = 1;
}
if(that._circleRingTwo.geometry.boundingSphere.radius<that.radius){
var radiusTwo = that._circleRingTwo.geometry.boundingSphere.radius + speed;
that._circleRingTwo.geometry = new THREE.CircleBufferGeometry( radiusTwo, that.segments );
that._circleRingTwo.material.opacity = 1-radiusTwo/that.radius;
}
else if(that._circleRingTwo.geometry.boundingSphere.radius>=that.radius){
that._circleRingTwo.geometry = new THREE.CircleBufferGeometry( 0.01, that.segments );
that._circleRingTwo.material.opacity = 1;
}
}
renderer.render( that.scene, that.camera );
};

//init Circle Animation
this._init();
}

/**
* Begin Animation
*/
AnimationCircle.prototype.BeginAnimation = function(){
this._animate();
this._ifAnimate = true;
}

/**
* Stop Animation
*/
AnimationCircle.prototype.StopAnimation = function(){
this._ifAnimate = false;
this._circleRingOne.material.opacity = 0;
this._circleRingTwo.material.opacity = 0;
}

/**
* Move Circle Position
* @param {*} position
*/
AnimationCircle.prototype.SetPosition = function(position){
this._circleMain.position = position;
this._circleRingOne.position = position;
this._circleRingTwo.position = position;
}

/**
* Rotation Circle
* @param {*} rotation
*/
AnimationCircle.prototype.SetRotation = function(rotation){
this._circleMain.rotation = rotation;
this._circleRingOne.rotation = rotation;
this._circleRingTwo.rotation = rotation;
}

/**
* Change Circle Radius
* @param {*} radius
*/
AnimationCircle.prototype.SetRadius = function(radius){
this.radius = radius;
this._circleMain.geometry = new THREE.CircleBufferGeometry( radius, this.segments );
}

/**
* Change Cicle Segments
* @param {*} segments
*/
AnimationCircle.prototype.SetSegments = function(segments){
this.segments = segments;
this._circleMain.geometry = new THREE.CircleBufferGeometry( this.radius, segments );
}

/**
* Change Circle Animation Speed
* @param {*} speed
*/
AnimationCircle.prototype.SetSpeed = function(speed){
this.speed = speed;
}

验证成果

下载引入库

首先是需要引用Three.js库。我这边是到到官网或github下载最新的Three.js库的,其实也可以以在线CDN的方式引入。
库CDN地址:https://cdn.bootcss.com/three.js/92/three.min.js

初始化场景

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var scene = new THREE.Scene();//创建三维场景
scene.background = new THREE.Color( 0xf0f0f0 );//设置背景颜色

//创建并设置相机
var camera=new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);
camera.position.z = 5;
camera.lookAt(scene.position);
scene.add(camera);

//创建并设置渲染器
var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

//初始化渲染场景
renderer.render( scene, camera );

引入插件

1
<script src="script/animationCircle.js"></script>

创建动画圆

1
2
var animationCircle = new AnimationCircle(scene,renderer, camera,new THREE.Vector3( 0, 0, 0),new THREE.Vector3( 0, 0, 0),1,64,0xFF0017,0.01);
animationCircle.BeginAnimation();

最终代码

展示代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<html>
<head>
<title>My first three.js app</title>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script src="js/three.js"></script>
<script src="script/animationCircle.js"></script>
<script>
var scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );

var camera=new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.1,1000);
camera.position.z = 5;
camera.lookAt(scene.position);
scene.add(camera);

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

//创建圆,并开始动画
var animationCircle = new AnimationCircle(scene,renderer, camera,new THREE.Vector3( 0, 0, 0),new THREE.Vector3( 0, 0, 0),1,64,0xFF0017,0.01);
animationCircle.BeginAnimation();

renderer.render( scene, camera );
</script>
</body>
</html>

插件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
* @Author: funcgis
* @Date: 2018-06-10 15:22:24
* @Last Modified by: funcgis
* @Last Modified time: 2018-06-10 16:48:51
* Three.js Animation Circle Plugin
*/

/**
* Animation Circle Object
* @param {*} scene Three.js scene Object
* @param {*} position Circle Animation's Position
* @param {*} rotation Circle Animation's Rotation
* @param {*} radius Circle's Radius
* @param {*} segments Circle's segments
* @param {*} color Circle's Color
* @param {*} speed Circle Animation's Speed
*/
var AnimationCircle = function(scene, renderer, camera, position, rotation, radius, segments, color, speed){
//properties
this.scene = scene;
this.renderer = renderer;
this.camera = camera;
this.radius = radius;
this.segments = segments;
this.color = color;
this.speed = speed;
this.position = position;
this.rotation = rotation;

this._ifAnimate = false;
this._circleMain = null;
this._circleRingOne = null;
this._circleRingTwo = null;

//init Circle Animation Objects
this._init = function(){
var circleBufferGeometry = new THREE.CircleBufferGeometry( this.radius, this.segments );
var circleRingOneGeometry = new THREE.CircleBufferGeometry( 0.001, this.segments );
var circleRingTwoGeometry = new THREE.CircleBufferGeometry( this.radius/2, this.segments );
var materialMain = new THREE.MeshBasicMaterial( { color: color, opacity: 0.3, transparent: true, side: THREE.DoubleSide} );
var materialRingOne = new THREE.MeshBasicMaterial( { color: color, opacity: 0, transparent: true, side: THREE.DoubleSide} );
var materialRingTwo = new THREE.MeshBasicMaterial( { color: color, opacity: 0, transparent: true, side: THREE.DoubleSide} );
materialMain.needsUpdate=true;
materialRingOne.needsUpdate=true;
materialRingTwo.needsUpdate=true;

this._circleMain = new THREE.Mesh( circleBufferGeometry, materialMain );

this._circleRingOne = new THREE.Mesh( circleRingOneGeometry, materialRingOne );
this._circleRingOne.material.opacity = 0;

this._circleRingTwo = new THREE.Mesh( circleRingTwoGeometry, materialRingTwo );
this._circleRingTwo.material.opacity = 0;

this.scene.add( this._circleMain );
this.scene.add( this._circleRingOne );
this.scene.add( this._circleRingTwo );
};

var that = this;
//Circle Animation function
this._animate = function(){
requestAnimationFrame( that._animate );
//do animate
if(that._ifAnimate){
if(that._circleRingOne.geometry.boundingSphere.radius<that.radius){
var radiusOne = that._circleRingOne.geometry.boundingSphere.radius + speed;
that._circleRingOne.geometry = new THREE.CircleBufferGeometry( radiusOne, that.segments );
that._circleRingOne.material.opacity = 1-radiusOne/that.radius;
}
else if(that._circleRingOne.geometry.boundingSphere.radius>=that.radius){
that._circleRingOne.geometry = new THREE.CircleBufferGeometry( 0.01, that.segments );
that._circleRingOne.material.opacity = 1;
}
if(that._circleRingTwo.geometry.boundingSphere.radius<that.radius){
var radiusTwo = that._circleRingTwo.geometry.boundingSphere.radius + speed;
that._circleRingTwo.geometry = new THREE.CircleBufferGeometry( radiusTwo, that.segments );
that._circleRingTwo.material.opacity = 1-radiusTwo/that.radius;
}
else if(that._circleRingTwo.geometry.boundingSphere.radius>=that.radius){
that._circleRingTwo.geometry = new THREE.CircleBufferGeometry( 0.01, that.segments );
that._circleRingTwo.material.opacity = 1;
}
}
renderer.render( that.scene, that.camera );
};

//init Circle Animation
this._init();
}

/**
* Begin Animation
*/
AnimationCircle.prototype.BeginAnimation = function(){
this._animate();
this._ifAnimate = true;
}

/**
* Stop Animation
*/
AnimationCircle.prototype.StopAnimation = function(){
this._ifAnimate = false;
this._circleRingOne.material.opacity = 0;
this._circleRingTwo.material.opacity = 0;
}

/**
* Move Circle Position
* @param {*} position
*/
AnimationCircle.prototype.SetPosition = function(position){
this._circleMain.position = position;
this._circleRingOne.position = position;
this._circleRingTwo.position = position;
}

/**
* Rotation Circle
* @param {*} rotation
*/
AnimationCircle.prototype.SetRotation = function(rotation){
this._circleMain.rotation = rotation;
this._circleRingOne.rotation = rotation;
this._circleRingTwo.rotation = rotation;
}

/**
* Change Circle Radius
* @param {*} radius
*/
AnimationCircle.prototype.SetRadius = function(radius){
this.radius = radius;
this._circleMain.geometry = new THREE.CircleBufferGeometry( radius, this.segments );
}

/**
* Change Cicle Segments
* @param {*} segments
*/
AnimationCircle.prototype.SetSegments = function(segments){
this.segments = segments;
this._circleMain.geometry = new THREE.CircleBufferGeometry( this.radius, segments );
}

/**
* Change Circle Animation Speed
* @param {*} speed
*/
AnimationCircle.prototype.SetSpeed = function(speed){
this.speed = speed;
}

源码下载

github地址:https://github.com/funcgis/AnimationCircle

坚持原创技术分享,您的支持将鼓励我继续创作!