preview
You can first experience the effect online ↓(please use PC to browse)
moayuisuda.github.io/moa-line/.
noise
An overview of the noise
Noise, as I understand it, is “continuous randomness”, such as noise(0.01), noise(0.02), noise(0.03)…… If the parameters are close, the resulting values will be random, but also close. If you want to further understand the principle of noise, recommend this article blog.csdn.net/candycat199…
Imagine an undulating sea. Is the height of the adjacent position consistent with this “continuous and random”?
Intuitive impression
The 3d noise simplex3 is used this time. You can imagine it as a large cube containing continuous random numbers in the range of -1 to 1. We input the coordinates of a cube (x, y, z) to pick out a random value, I use three.js to generate a cube to visually display the characteristics of noise ↓
const span = 100; // Cube length
// To get continuous random values, the parameters passed in must also differ very closely, usually within the order of 0.1.
const scale = 100;
/* scale = 100 represents a three.js unit length map of 0.01(1/100) noisy cube unit length. In other words, the cube generated in three.js is 100*100*100, but it only maps the situation of 1*1*1 in the noisy cube. * /
for (let z = 0; z < span; z++) {
for (let y = 0; y < span; y++) {
for (let x = 0; x < span; x++) {
let point = new THREE.Vector3(x, y, z); // Each point is a point that makes up the cube
// for example, when x,y,z are (4,5,5), the mapping noise cube coordinates are (0.04, 0.05, 0.05).
// noise.simplex3() has the range -1 to 1, and the seed range is 0 to 1
let seed = 0.5 * (noise.simplex3(x / scale, y / scale, z / scale) + 1);
// * Color will reflect the seed directly, where the three parameters range from 0 to 1(1 equals 255). The darker the color, the smaller the value
let color = newTHREE.Color(seed, seed, seed); Geometry.vertices.push(point); Geometry.colors.push(color); }}}Copy the code
let seed = 0.5 * (noise.simplex3(x / scale, y / scale, z / scale) + 1);
let color = new THREE.Color(seed, seed, seed); // The darker the color, the smaller the value
Copy the code
And you can see that the color change is not that big anymore, because scale is 10 times bigger, and x/scale, y/scale, z/scale is 10 times smaller between x, y, and z next to each other, Simplex3 (x/scale, y/scale, z/scale) is also very small, and color is also very small.
Began to
Let’s draw the circle first
<! DOCTYPE html><html>
<head>
<style>
body {
margin: 0;
}
.main {
background: # 000;
height: 100vh;
}
</style>
</head>
<body>
<div class="main"></div>
</body>
<script>
const wave = function({
dom, //Which DOM is mounted on span =50.//The size of a single element zIndex = -999 //Canvas z - index}) {
// Change the parent DOM to a cascading context
dom.style.position = "relative";
dom.style.zIndex = 0;
dom.style.overflow = "hidden";
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
If the parent element is not a cascading context, the canvas will be directly covered by the parent element.
canvas.style.position = "absolute";
canvas.style.zIndex = zIndex;
canvas.height = parseInt(getComputedStyle(dom)["height"]);
canvas.width = parseInt(getComputedStyle(dom)["width"]);
dom.appendChild(canvas);
let r = span / 2; // The radius of a single element
// Single registration point
function Point({ cx, cy }) {
this.cx = cx;
this.cy = cy;
}
// The draw method of the anchor point, which is used to generate shapes, is now simply used to draw circles by arc
Point.prototype.draw = function() {
context.beginPath();
context.strokeStyle = "#ffffff";
context.arc(this.cx, this.cy, r, 0.2 * Math.PI);
context.stroke();
};
// Initialize all registration points in the points array
let points = [];
function initPoints() {
for (let y = 0; y < canvas.height; y += span) {
for (let x = 0; x < canvas.width; x += span) {
X < canvas.height is true even on the penultimate line of the canvas and continues downward
points.push(
new Point({
cx: x + r,
cy: y + r }) ); }}}// Each time requestAnimationFrame calls the method to redraw the cloth
function draw() {
context.clearRect(0.0, canvas.width, canvas.height); // Empty the canvas
// Call the draw method on each point
for (let i ofpoints) { i.draw(); }}let id; / / animation id
function animate() {
draw();
id = requestAnimationFrame(animate);
}
initPoints();
animate();
// Return the canvas and stop methods to stop the animation
return {
canvas,
stop(){ cancelAnimationFrame(id); }}; };/ / run
wave({
dom: document.querySelector(".main")});</script>
</html>
Copy the code
After running, you should see the following ↓
The introduction of noise
First of all, we need to introduce noise library, please copy and bring raw.githubusercontent.com/josephg/noi script…
For each point, we need to use its Cx and cy properties to map to the coordinates in the noise cube, but one is planar and the other is three-dimensional. Simplex3 (x, y, 0) is a two-dimensional noise. Simplex3 (x, y, 0) is a two-dimensional noise.
First add a scale to the parameters of the function, that is, the mapping ratio with the noise cube mentioned above
const wave = function({
dom, //Which DOM is mounted on span =50.//Size of a single element scale =1000.//----------------- the previous mapping ratio with the noise cube zIndex = -999 //Canvas z - index})
Copy the code
Modify the above point.prototype.draw
Point.prototype.draw = function() {
context.beginPath();
let seed = 0.5 * (noise.simplex3(this.cx / scale, this.cy / scale, 0) + 1); // Seed range 0-1
context.strokeStyle = `rgba(255, 255, 255, ${seed}) `; // Use seed as alpha
context.arc(this.cx, this.cy, r, 0.2 * Math.PI);
context.stroke();
}
Copy the code
Re-run it and you should see something like ↓ below
The opacity of the circle becomes “random and continuous”.
Let’s get it moving
We wrote animate() above, but since the animate() function does not change the properties of the graph, it looks static.
Remember that we set the third parameter of the noise function to zero ↓
noise.simplex3(this.cx / scale, this.cy / scale, 0)
And now that we’ve enabled it again, what’s the value for it? Time.
Let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, 0) + 1); let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, 0) + 1); Let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, time) + 1); let seed = 0.5 * (noise.simplex3(this.cx/scale, this.cy/scale, time) + 1);
We need to define a global variabletime
, and a parameter that controls the speed of timespeed
And in animatetime += speed;
const wave = function({
dom, //Which DOM is mounted on span =50.//Size of a single element scale =1000.//The mapping ratio to the noisy cube is speed =0.01.//-----------------speed parameter zIndex = -999 //Canvas z - index})
Copy the code
let id; / / animation id
let time = 0; // ------------- initializes time to 0
function animate() {
draw();
time += speed;
id = requestAnimationFrame(animate);
}
Copy the code
Then you will notice your picture moving down
Now that all the basic code is done, the next step is yesPoint.prototype.draw
With various modifications, let’s implement a line animation
Point.prototype.draw = function() {
context.beginPath();
let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
let sa = Math.abs(s); // The absolute value of noise, used to generate alpha and lineWidth
context.strokeStyle = `rgba(255, 255, 255, ${sa}) `;
context.lineWidth = Math.abs(s) * 8;
let a = Math.PI * 2 * s; / / Angle
let ap = Math.PI + a; // Angle + 180 degrees
context.moveTo(this.cx + Math.cos(a) * r, this.cy);
context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);
context.stroke();
};
Copy the code
context.moveTo(this.cx + Math.cos(a) * r, this.cy); Context.moveto (this.cx + math.cos (a) * r, this.cy + math.sin (a)); And you could try it out and replace it with the latter, and it would be a lot less interesting, because you would just be connecting one end of the circle to the other end.
By adjusting speed, scale, SPAN and other parameters, you can get many interesting effects ↓
Then use your imagination to transformPoint.prototype.draw
!
For the complete code for this example, remember to change the SRC ↓ of the noise library
<! DOCTYPEhtml>
<html>
<head>
<style>
body {
margin: 0;
}
.main {
background: # 000;
height: 100vh;
}
</style>
</head>
<body>
<div class="main"></div>
</body>
<script src="./perlin.js"></script>
<script>
const wave = function({
dom,
span = 50,
scale = 1000,
speed = 0.01,
zIndex = -999
}) {
dom.style.position = "relative";
dom.style.zIndex = 0;
dom.style.overflow = "hidden";
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.zIndex = zIndex;
canvas.height = parseInt(getComputedStyle(dom)["height"]);
canvas.width = parseInt(getComputedStyle(dom)["width"]);
dom.appendChild(canvas);
let r = span / 2;
function Point({ cx, cy }) {
this.cx = cx;
this.cy = cy;
}
Point.prototype.draw = function() {
context.beginPath();
let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
let sa = Math.abs(s);
context.strokeStyle = `rgba(255, 255, 255, ${sa}) `;
context.lineWidth = Math.abs(s) * 8;
let a = Math.PI * 2 * s;
let ap = Math.PI + a;
context.moveTo(this.cx, this.cx);
context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);
context.stroke();
};
let points = [];
function initPoints() {
for (let y = 0; y < canvas.height; y += span) {
for (let x = 0; x < canvas.width; x += span) {
points.push(
new Point({
cx: x + r,
cy: y + r }) ); }}}function draw() {
context.clearRect(0.0, canvas.width, canvas.height);
for (let i ofpoints) { i.draw(); }}let id;
let time = 0;
function animate() {
draw();
time += speed;
id = requestAnimationFrame(animate);
}
initPoints();
animate();
return {
canvas,
stop(){ cancelAnimationFrame(id); }}; }; wave({dom: document.querySelector(".main"),
scale: 5000.speed: 0.002.span: 100
});
</script>
</html>
Copy the code
Color gradient
Next, we will implement the DOM color transformation by clicking on it, and can customize the color transformation array, color conversion time. Let’s start with the new parameters:
const wave = function({
dom,
span = 50,
scale = 1000,
speed = 0.01,
zIndex = -999,
duration = 2000.//-------------- color conversion time colors = [[212.192.255],
[192.255.244],
[255.192.203]]//------------- color array})
Copy the code
The gradient
First we need to achieve the gradient, and also need to be able to customize the gradient time, write a general linear gradient function ↓
const copy = target= > {
let re = [];
for (let i in target) {
re[i] = target[i];
}
return re;
};
const move = (
origin,
target,
duration,
after, // Callback after each value change
fn = pro => {
return Math.sqrt(pro, 2);
} // slow function
) = > {
if (fn(1) != 1) throw '[moaline-move] The fn must satisfy "fn (1) == 1"'; // When the parameter is 1, the corresponding value must also be 1
let st, sp;
st = performance.now(); // Save the start time
sp = copy(origin); // Save the source attributes
let d = {}; // Distance of each term between source and target
for (let i in origin) {
d[i] = target[i] - origin[i];
}
let frame = t= > {
let pro = (t - st) / duration; // The current process
if (pro >= 1) {
return;
}
for (let i in origin) {
origin[i] = sp[i] + fn(pro) * d[i]; // fn(pro) calculates the distance percentage of the current time corresponding to the easing function, and then multiplies the total distance
}
if(after) after(copy(origin), pro);
requestAnimationFrame(frame);
};
frame(st);
};
Copy the code
The binding event
Then we bind an event to the DOM and let it do the tweening with one click.
let ci = 0;
const color = [...colors[ci]];
function clickE() {
let target = [...colors[++ci % colors.length]];
move(color, target, duration);
}
dom.addEventListener("click", clickE);
Copy the code
Numerical binding
Now that color can be tweaked, bind it to the color related part of the point.prototype. draw.
Point.prototype.draw = function() {
context.beginPath();
let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
let sa = Math.abs(s);
context.strokeStyle = `rgba(${color[0]}.${color[1]}.${color[2]}.${sa}) `;
context.lineWidth = Math.abs(s) * 8;
let a = Math.PI * 2 * s;
let ap = Math.PI + a;
context.moveTo(this.cx + Math.sin(a) * r, this.cy + Math.cos(a) * r);
context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);
context.stroke();
};
Copy the code
Then remove the background color of the body
.main {
/* background: #000; */
height: 100vh;
}
Copy the code
The complete code is as follows (remember to change the SRC of the Noise library yourself)
<! DOCTYPEhtml>
<html>
<head>
<style>
body {
margin: 0;
}
.main {
/* background: #000; * /
height: 100vh;
}
</style>
</head>
<body>
<div class="wrapper"></div>
<div class="main"></div>
</body>
<script src="./perlin.js"></script>
<script>
const wave = function({
dom,
span = 50,
scale = 1000,
speed = 0.01,
zIndex = -999,
duration = 2000,
colors = [
[212.192.255],
[192.255.244],
[255.192.203]]}) {
dom.style.position = "relative";
dom.style.zIndex = 0;
dom.style.overflow = "hidden";
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.zIndex = zIndex;
canvas.height = parseInt(getComputedStyle(dom)["height"]);
canvas.width = parseInt(getComputedStyle(dom)["width"]);
dom.appendChild(canvas);
let r = span / 2;
function Point({ cx, cy }) {
this.cx = cx;
this.cy = cy;
}
Point.prototype.draw = function() {
context.beginPath();
let s = noise.simplex3(this.cx / scale, this.cy / scale, time);
let sa = Math.abs(s);
context.strokeStyle = `rgba(${color[0]}.${color[1]}.${color[2]}.${sa}) `;
context.lineWidth = Math.abs(s) * 8;
let a = Math.PI * 2 * s;
let ap = Math.PI + a;
context.moveTo(this.cx + Math.sin(a) * r, this.cy + Math.cos(a) * r);
context.lineTo(this.cx + Math.cos(ap) * r, this.cy + Math.sin(ap) * r);
context.stroke();
};
let points = [];
function initPoints() {
for (let y = 0; y < canvas.height; y += span) {
for (let x = 0; x < canvas.width; x += span) {
points.push(
new Point({
cx: x + r,
cy: y + r }) ); }}}function draw() {
context.clearRect(0.0, canvas.width, canvas.height);
for (let i ofpoints) { i.draw(); }}let id;
let time = 0;
function animate() {
draw();
time += speed;
id = requestAnimationFrame(animate);
}
let ci = 0;
const color = [...colors[ci]];
function clickE() {
let target = [...colors[++ci % colors.length]];
move(color, target, duration);
}
dom.addEventListener("click", clickE);
const copy = target= > {
let re = [];
for (let i in target) {
re[i] = target[i];
}
return re;
};
const move = (
origin,
target,
duration,
after, // Callback after each value change
fn = pro => {
return Math.sqrt(pro, 2);
} // slow function
) = > {
if (fn(1) != 1) throw '[moaline-move] The fn must satisfy "fn (1) == 1"'; // When the parameter is 1, the corresponding value must also be 1
let st, sp;
st = performance.now(); // Save the start time
sp = copy(origin); // Save the source attributes
let d = {}; // Distance of each term between source and target
for (let i in origin) {
d[i] = target[i] - origin[i];
}
let frame = t= > {
let pro = (t - st) / duration; // The current process
if (pro >= 1) {
return;
}
for (let i in origin) {
origin[i] = sp[i] + fn(pro) * d[i]; // fn(pro) calculates the distance percentage of the current time corresponding to the easing function, and then multiplies the total distance
}
if(after) after(copy(origin), pro);
requestAnimationFrame(frame);
};
frame(st);
};
initPoints();
animate();
return {
canvas,
stop() {
cancelAnimationFrame(id);
dom.removeEventListener('click', clickE); }}; }; wave({dom: document.querySelector(".main"),
scale: 1000.speed: 0.0006.span: 200.duration: 1000
});
</script>
</html>
Copy the code
The last
Use your imagination, use the generated value of the noise flexibly, or add some interaction, and the effect will be more powerful. But be sure to call the return stop() method to stop the animation when you don’t need it.
Can also be multiple canvas overlay.