Coco Ant Financial · Data Experience Technology team
Results demonstrate
3D particle animations look like this: Embed: sky.mov
The composition of this example is not complicated, mainly divided into the following parts:
- Gradient background
- Constantly switching 3D particle model (entrance scatter is also a form of particle model)
- Switch text at the same time
The implementation is mostly based on Threejs, and I’ll go through each part of the implementation separately, but not the basics. The basic content can be found on the official website
Gradient background
The scene’s background can accept Color, Texture, or CubeTexture. In this example, we use Texture to achieve the gradient effect. The Texture accepts either a canvas or an image. Here we use the Canvas to create a gradient effect.
const canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const context = canvas.getContext('2d');
const gradient = context.createLinearGradient(0.0, width, 0);
gradient.addColorStop(0.'#4e22b7');
gradient.addColorStop(1.'#3292ff');
context.fillStyle = gradient;
context.fillRect(0.0, canvas.width, canvas.height);
const texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
const scene = new THREE.Scene();
scene.background = texture;
Copy the code
Reduce model particle number
Next is a black technology to reduce the number of particles in the model. Usually the designer will give you a model that meets the requirements in the design, with no more particles than you need, but there are exceptions. In addition, the number of particles is one of the key factors affecting performance. In this example, I have tested 1W particles to make the animation feel sluggish (of course, this also depends on the performance of the running book). If you happen to have this kind of trouble, then this method may help you.
In the X,Y,Z axis respectively calculate the distance between adjacent points, if the distance is greater than the set value is deleted. The specific code is as follows:
{x, y,z} const all = []; const yObjs = {}; const xObjs = {}; const zObjs = {}; XObjs,yObjs,zObjs for (var I = pos.length-1; i>=0; i--) { const p = pos[i]; const yKey = getKey(p.y); const xKey = getKey(p.x); const zKey = getKey(p.z); if (! yObjs[yKey]) { yObjs[yKey] = []; } if (! xObjs[xKey]) { xObjs[xKey] = []; } if (! zObjs[zKey]) { zObjs[zKey] = []; } const item = {x, y, z, r, g, b}; yObjs[yKey].push(item); xObjs[xKey].push(item); zObjs[zKey].push(item); all.push(item); } // Order x,y,z values const xKeys = orderAscKeys(xObjs); const yKeys = orderAscKeys(yObjs); const zKeys = orderAscKeys(zObjs); Const xDels = getDelKeys(xKeys, 4); // Const xDels = getDelKeys(xKeys, 4); Const yDels = getDelKeys(yKeys, 4); // const yDels = getDelKeys(yKeys, 4); const zDels = getDelKeys(zKeys, 4); const result = []; for (var i = all.length-1; i>=0; i--) { const item = all[i]; if ( ! (xDels.indexOf(getKey(item.x))>-1 || yDels.indexOf(getKey(item.y))>-1 || zDels.indexOf(getKey(item.z))>-1 )) { result.push(item); } } return result; function getKey(x) { let r = x.toFixed(1); If (r==='-0.0') {r= '0.0'} return r; } function orderAscKeys (obj) { return Object.keys(obj).sort((a, b)=>{ return Number(a) - Number(b); }); } function getDelKeys(keys, d) { const dels = []; keys.reduce((prev, curr, idx)=>{ let res = curr; if (curr-prev < d) { dels.push(curr); res = prev; } return res; }); return dels; }Copy the code
Note: This method can only be used for models with neat particle order. If the particle order of the model itself is chaotic, the effect of this method may not be ideal.
Model switching animation
Creating a particle object
Throughout the animation, particles are smoothly grouped together by shifting positions. How do you do this? If you give new Geometry to every model, you will get confused and the results will not be ideal. A better way to deal with these five models is to use a Geometry, and transform the model by changing the Vertices attribute. The vertices attribute changes the value of each of the vertices, but it doesn’t add the number of vertices to the vertices. This requires that the vertices of our initial definition of Geometry must be the one with the largest number of points among several models.
const config = this.config;
const textureLoader = new THREE.TextureLoader();
textureLoader.crossOrigin = ' ';
const mapDot = textureLoader.load('img/gradient.png'); / / dot
this.mapDot = mapDot;
const geometry = new THREE.Geometry();
const count = config.totalCount; // Maximum number of particles in four models
for (let i = 0; i < count; i++) {
let x = 0, y = 0, z = 0;
x = Math.random() * 2000 - 1000;
y = Math.random() * 2000 - 1000;
z = Math.random() * 8000 - 4000;
geometry.vertices.push(new THREE.Vector3(x, y, z));
geometry.colors.push(new THREE.Color(1.1.1));
}
const material = new THREE.PointsMaterial({
size: 20.map: mapDot,
depthTest: true.alphaTest: 1..opacity: 1.side: THREE.DoubleSide,
transparent:!0.vertexColors: THREE.VertexColors,
});
const points = new THREE.Points(geometry, material);
// Adjust the model pose
points.rotation.z = Math.PI;
points.rotation.y = Math.PI;
points.rotation.x = -Math.PI * 3.;
points.position.y = 240;
points.position.x = 100;
points.position.z = 240;
this.scene.add(points);
this.points = points;
Copy the code
WebGL adopts right-handed coordinate system, while canvas two-dimensional coordinate system extends x axis to the right and Y axis to the bottom. Because of such differences, the 3D model generated directly from pictures will be inverted. In the actual processing, we can make adjustments through the arrangement of the model, and also convert the coordinate values of each point. Specific practices can be selected according to the actual desired effect.
Differences in coordinate systems, as shown in the figure:
State switch
There are several states in the animation process:
- Introductory animation (i.e., scatter state) for data preparation and animation transition effects
- Model switch animation is in progress
- Model switching interval rest
The specific code of state management and switch is as follows:
// All the models are ready
if (this.objectsData && this.points && this.index ! = =undefined) {
const config = this.config;
const len = config.objs.length;
const idx = this.index % len; // The current model number
const item = this.objectsData[idx];
const geometry = this.points.geometry;
this.points.material.needsUpdate = true;
geometry.verticesNeedUpdate = true;
geometry.colorsNeedUpdate = true;
// No additional delay is required for prelude because the data preparation time is not long
const delay = (this.index === 0)? config.delay *60 * 0 : config.delay * 60;
if (item.waiting < delay) {
// Wait time, including prelude (1) and switch interval (3)
item.waiting++;
} else if (item.waiting >= delay && item.count < config.totalCount - 1) {
// When the animation is running (2)
let nd;
if (item.count === 0) {
config.onLeave && typeof config.onLeave === 'function' && config.onLeave();
nd = idx;
const prevIdx = (idx - 1 > - 1)? idx -1 : len - 1;
const prev = this.objectsData[prevIdx];
if (prev) {
// When the next model switch animation is executed, the previous run-time count and the rest count are reset to zero
prev.count = 0;
prev.waiting = 0; }}// Perform the animation
this.particleAnimation(this.points, item, nd);
} else if (item.waiting >= delay && item.count >= config.totalCount - 1) {
// Switch to the next model
this.index++; }}Copy the code
Perform the animation
Model switch animation is executed in batches, not all particles start moving together. The benefits of this are:
- Avoid the image lag feeling due to the large amount of calculation in unit time (16.67ms); The unit time is determined by the frequency of requestAnimationFrame execution.
- Group animations give animation a more layered feel
/** * points: particle object * item: current model data * IDx: current model number */
particleAnimation(points, item, idx) {
const geometry = points.geometry;
const vertices = geometry.vertices;
const colors = geometry.colors;
const len = vertices.length;
if (item.count >= len) { return; }
if(! item.vTween) { item.vTween = []; }const config = this.config;
let isOdd = this.index % 2= = =0;
// The number of particles executed per group of animations
const cnt = 1000;
for (let j = item.count, l = item.count + cnt; j < l && j < len - 1; j++) {
const n = j % item.length;
const p = item.data[n];
if(! p) {return; }
// TWEEN only new once to reduce the overhead of multiple instantiations
if(! item.vTween[j] && vertices[j]) { item.vTween.push(new TWEEN.Tween(vertices[j])
.easing(TWEEN.Easing.Exponential.In)
);
}
item.vTween[j].stop();
// If the model position is different, adjust the x coordinate data
const x = (isOdd) ? p.x : p.x- 400.;
item.vTween[j].to({ x, y: p.y, z: p.z }, config.speed).start();
}
item.count = item.count + cnt;
}
Copy the code
TWEEN is a library for TWEEN animation. It is easy to use, see the documentation for details. TWEEN provides the following ways and can be used by itself, as shown in the figure:
conclusion
Text part of the animation is achieved with CSS3, the network has a lot of cSS3 animation related documents will not be detailed, here is a simple list of relevant style code for reference.
.leaving {
transition: transform .7s, opacity .7s;
transform: translate3d(0px, -205%, 0);
opacity: 0;
}
.entering {
transition: opacity .7s;
opacity: 1;
}
Copy the code
So far the whole animation effect may encounter problems have been explained one by one, interested students can automatically start to try. If there is a better way to achieve welcome to share.
If you are interested, you can follow the column or send your resume to ‘shanshan.hongss####alibaba-inc.com’. Replace (‘####’, ‘@’)
Original address: github.com/ProtoTeam/b…