1.效果图(录屏转码模糊,实际效果可访问kuangstudy.com)

注:效果源自kuangstudy.com,在此仅作学习记录

2.代码实现
HTML:
两个js文件顺序不能改变,要先加载./js/zrveeq.js
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body class="home-page" style=" margin: 0 auto;background-color: #2196F3;">
</body>
<script src="./js/zrveeq.js"></script>
<script src="./js/index.js"></script>
</html>
index.js:
let canvas, ctx;
let render, init;
let surface;class Noise {constructor(r) {if (r == undefined) r = Math;this.grad3 = [[1, 1, 0], [-1, 1, 0], [1, -1, 0], [-1, -1, 0],[1, 0, 1], [-1, 0, 1], [1, 0, -1], [-1, 0, -1],[0, 1, 1], [0, -1, 1], [0, 1, -1], [0, -1, -1]];this.p = [];for (var i = 0; i < 256; i++) {this.p[i] = Math.floor(r.random() * 256);}// To remove the need for index wrapping, double the permutation table lengththis.perm = [];for (var i = 0; i < 512; i++) {this.perm[i] = this.p[i & 255];}}dot(g, x, y, z) {return g[0] * x + g[1] * y + g[2] * z;}mix(a, b, t) {return (1.0 - t) * a + t * b;}fade(t) {return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);}noise(x, y, z) {// Find unit grid cell containing pointvar X = Math.floor(x);var Y = Math.floor(y);var Z = Math.floor(z);// Get relative xyz coordinates of point within that cellx = x - X;y = y - Y;z = z - Z;// Wrap the integer cells at 255 (smaller integer period can be introduced here)X = X & 255;Y = Y & 255;Z = Z & 255;// Calculate a set of eight hashed gradient indicesvar gi000 = this.perm[X + this.perm[Y + this.perm[Z]]] % 12;var gi001 = this.perm[X + this.perm[Y + this.perm[Z + 1]]] % 12;var gi010 = this.perm[X + this.perm[Y + 1 + this.perm[Z]]] % 12;var gi011 = this.perm[X + this.perm[Y + 1 + this.perm[Z + 1]]] % 12;var gi100 = this.perm[X + 1 + this.perm[Y + this.perm[Z]]] % 12;var gi101 = this.perm[X + 1 + this.perm[Y + this.perm[Z + 1]]] % 12;var gi110 = this.perm[X + 1 + this.perm[Y + 1 + this.perm[Z]]] % 12;var gi111 = this.perm[X + 1 + this.perm[Y + 1 + this.perm[Z + 1]]] % 12;// The gradients of each corner are now:// g000 = grad3[gi000];// g001 = grad3[gi001];// g010 = grad3[gi010];// g011 = grad3[gi011];// g100 = grad3[gi100];// g101 = grad3[gi101];// g110 = grad3[gi110];// g111 = grad3[gi111];// Calculate noise contributions from each of the eight cornersvar n000 = this.dot(this.grad3[gi000], x, y, z);var n100 = this.dot(this.grad3[gi100], x - 1, y, z);var n010 = this.dot(this.grad3[gi010], x, y - 1, z);var n110 = this.dot(this.grad3[gi110], x - 1, y - 1, z);var n001 = this.dot(this.grad3[gi001], x, y, z - 1);var n101 = this.dot(this.grad3[gi101], x - 1, y, z - 1);var n011 = this.dot(this.grad3[gi011], x, y - 1, z - 1);var n111 = this.dot(this.grad3[gi111], x - 1, y - 1, z - 1);// Compute the fade curve value for each of x, y, zvar u = this.fade(x);var v = this.fade(y);var w = this.fade(z);// Interpolate along x the contributions from each of the cornersvar nx00 = this.mix(n000, n100, u);var nx01 = this.mix(n001, n101, u);var nx10 = this.mix(n010, n110, u);var nx11 = this.mix(n011, n111, u);// Interpolate the four results along yvar nxy0 = this.mix(nx00, nx10, v);var nxy1 = this.mix(nx01, nx11, v);// Interpolate the two last results along zvar nxyz = this.mix(nxy0, nxy1, w);return nxyz;}}class Surface {constructor(points = 5, dimensions) {this.stage = document.createElement('canvas');this.stage.id = "surfaceCanvas";this.t = 0;this.noise = new Noise();this.dimensions = dimensions;this.initialise();this.onMouseMove = this.onMouseMove.bind(this);window.addEventListener('pointermove', this.onMouseMove);this.numPoints = points;this.running = true;}initialise() {this.points = [];for (let i = 0; i <= this.numPoints; i++) {this.points.push(new SurfacePoint(i, undefined, Math.random() * .5));}// window.p = this.points[50];}draw(ops, ctx) {ops.forEach(op => {ctx[op.name](...op.params);});}render(delta) {let ctx = this.stage.getContext('2d');let y = 0;ctx.clearRect(0, 0, this.width, this.height);let ops = [];ops.push({name: 'beginPath',params: [] });ops.push({name: 'moveTo',params: [0, 0] });ops.push({name: 'lineTo',params: [0, this.height * .5] });y = this.height * .5;this.t += .015;// let right = this.points[1];this.points.forEach((point, index) => {let left1 = this.points[index - 1];let right1 = this.points[index + 1];let left2 = this.points[index - 2];let right2 = this.points[index + 2];let left1Height = left1 ? left1.height : 0;let right1Height = right1 ? right1.height : 0;let left2Height = left2 ? left2.height : 0;let right2Height = right2 ? right2.height : 0;// accelerationpoint.acceleration = (-0.3 * point.height + (left1Height - point.height) + (right1Height - point.height)) * this.elasticity - point.speed * this.friction;point.acceleration += (-0.3 * point.height + (left2Height - point.height) + (right2Height - point.height)) * (this.elasticity / 2) - point.speed * this.friction;// speedpoint.speed += point.acceleration * 5;// heightpoint.height += point.speed * 10;let p1 = new Vector(this.segWidth * (index - 1), y + left1Height);let p2 = new Vector(this.segWidth * index, y + point.height);var xc = (p1.x + p2.x) / 2;var yc = (p1.y + p2.y) / 2;ops.push({name: 'quadraticCurveTo',params: [p1.x, p1.y, xc, yc] });let sp = this.noise.noise(p1.x * .01, p1.y * .01, this.t);sp *= sp;point.speed += sp * .05;});let p1 = new Vector(this.segWidth * this.numPoints, y + this.points[this.points.length - 1].height);let p2 = new Vector(this.segWidth * (this.numPoints + 1), y);var xc = (p1.x + p2.x) / 2;var yc = (p1.y + p2.y) / 2;ops.push({name: 'quadraticCurveTo',params: [p1.x, p1.y, xc, yc] });ops.push({name: 'lineTo',params: [this.width, 0] });ops.push({name: 'lineTo',params: [0, 0] });ops.push({name: 'closePath',params: [] });ctx.globalCompositeOperation = 'source-over';ctx.fillStyle = this.colour;ctx.fillStyle = this.gradient;ctx.strokeStyle = 'rgba(255,255,255,1)';this.draw(ops, ctx);ctx.fill();// ctx.stroke();ctx.globalCompositeOperation = 'multiply';ctx.shadowOffsetX = 0;ctx.shadowOffsetY = -10;ctx.shadowBlur = 20;ctx.shadowColor = 'rgba(0,80,0,1)';this.draw(ops, ctx);ctx.stroke();ctx.stroke();ctx.stroke();if (this.running) {window.requestAnimationFrame(this.render.bind(this));}}onMouseMove(e) {e.preventDefault();let mousePos = new Vector(e.clientX, e.clientY); // The 250 here is just to make up for the offset on screenlet difference = this.oldMousePos.subtractNew(mousePos);let offset = this.stage.getBoundingClientRect();let normalisedPos1 = mousePos.y - (offset.top + this.height / 2);let normalisedPos2 = this.oldMousePos.y - (offset.top + this.height / 2);let changed = normalisedPos1 * normalisedPos2 < 0;if (changed) {let closestPointIndex = Math.round(mousePos.x / (this.width / this.numPoints));let closestPoint = this.points[closestPointIndex];let power = Math.min(Math.max(difference.y * .2, -1), 1);closestPoint.speed += -power;}this.oldMousePos = mousePos;}set oldMousePos(value) {if (value instanceof Vector) {this._oldMousePos = value;}}get oldMousePos() {return this._oldMousePos instanceof Vector ? this._oldMousePos : new Vector(0, 0);}set elasticity(value) {if (typeof value === 'number') {this._elasticity = value;}}get elasticity() {return this._elasticity || 0.00007;}set friction(value) {if (typeof value === 'number') {this._friction = value;}}get friction() {return this._friction || 0.0045;}set numPoints(value) {let oldNumPoints = this._numPoints;if (typeof value == 'number' && oldNumPoints != value) {this._numPoints = value;this.initialise();}}get numPoints() {return this._numPoints;}set running(value) {let oldValue = this._running;this._running = value === true;if (value === true && oldValue !== true) {this.render();}}get running() {return this._running === true;}set stage(element) {if (element instanceof HTMLElement) {this._stage = element;}}get stage() {return this._stage;}get segWidth() {return this.width / this.numPoints;}set dimensions(value) {if (value instanceof Vector) {this._dimensions = value;this.width = value.width;this.height = value.height;}}get dimensions() {return this._dimensions || null;}set width(value) {if (typeof value == 'number') {this._width = value;this.stage.width = this._width;}}get width() {return this._width || window.innerWidth;}set height(value) {if (typeof value == 'number') {this._height = value;this.stage.height = this._height;this.gradient = this.stage.getContext('2d').createLinearGradient(0, 0, 0, value * .5);// Add color stopsthis.gradient.addColorStop(0, '#2196F3');this.gradient.addColorStop(1, '#4cbbed');}}get height() {return this._height || window.innerHeight;}set colour(value) {this._colour = value;}get colour() {return this._colour || "#18d618";}}class SurfacePoint {constructor(index, acceleration, speed, height) {this.index = index;this.acceleration = acceleration;this.speed = speed;this.height = height;}set height(value) {if (typeof value == 'number') {this._height = value;// this._height = value > 300 ? 300 : (value < -300 ? -300 : value);}}get height() {return typeof this._height == 'number' ? this._height : 0;}set speed(value) {if (typeof value == 'number') {this._speed = Math.min(Math.max(value, -2), 2);}}get speed() {return typeof this._speed == 'number' ? this._speed : 0;}set acceleration(value) {if (typeof value == 'number') {this._acceleration = value;}}get acceleration() {return typeof this._acceleration == 'number' ? this._acceleration : 0;}}surface = new Surface(window.innerWidth / 50, new Vector(window.innerWidth, window.innerHeight));
surface.colour = '#000000';window.addEventListener('resize', () => {surface.dimensions = new Vector(window.innerWidth, window.innerHeight);
});document.body.appendChild(surface.stage);
zrveep.js
const conversionFactor = 180 / Math.PI;let radianToDegrees = function(radian) {return radian * conversionFactor;
}
let degreesToRadian = function(degrees) {return degrees / conversionFactor;
}// Taken from https://github.com/wethegit/wtc-vector
/*** A basic 2D Vector class that provides simple algebraic functionality in the form* of 2D Vectors.** We use Getters/setters for both principle properties (x & y) as well as virtual* properties (rotation, length etc.).** @class Vector* @author Liam Egan <liam@wethecollective.com>* @version 0.5* @created Dec 19, 2017*/
class Vector {/*** The Vector Class constructor** @constructor* @param {number} x The x coord* @param {number} y The y coord*/constructor(x, y){this.x = x;this.y = y;}/*** Resets the vector coordinates** @public* @param {number} x The x coord* @param {number} y The y coord*/reset(x, y) {this.x = x;this.y = y;}resetToVector(v) {if(v instanceof Vector) {this.x = v.x;this.y = v.y;}}/*** Clones the vector** @public* @return {Vector} The cloned vector*/clone() {return new Vector(this.x, this.y);}/*** Adds one vector to another.** @public* @chainable* @param {Vector} vector The vector to add to this one* @return {Vector} Returns itself, modified*/add(vector) {this.x += vector.x;this.y += vector.y;return this;}/*** Clones the vector and adds the vector to it instead** @public* @chainable* @param {Vector} vector The vector to add to this one* @return {Vector} Returns the clone of itself, modified*/addNew(vector) {let v = this.clone();return v.add(vector);}/*** Adds a scalar to the vector, modifying both the x and y** @public* @chainable* @param {number} scalar The scalar to add to the vector* @return {Vector} Returns itself, modified*/addScalar(scalar) {return this.add(new Vector(scalar, scalar));}/*** Clones the vector and adds the scalar to it instead** @public* @chainable* @param {number} scalar The scalar to add to the vector* @return {Vector} Returns the clone of itself, modified*/addScalarNew(scalar) {let v = this.clone();return v.addScalar(scalar);}/*** Subtracts one vector from another.** @public* @chainable* @param {Vector} vector The vector to subtract from this one* @return {Vector} Returns itself, modified*/subtract(vector) {this.x -= vector.x;this.y -= vector.y;return this;}/*** Clones the vector and subtracts the vector from it instead** @public* @chainable* @param {Vector} vector The vector to subtract from this one* @return {Vector} Returns the clone of itself, modified*/subtractNew(vector) {let v = this.clone();return v.subtract(vector);}/*** Subtracts a scalar from the vector, modifying both the x and y** @public* @chainable* @param {number} scalar The scalar to subtract from the vector* @return {Vector} Returns itself, modified*/subtractScalar(scalar) {return this.subtract(new Vector(scalar, scalar));}/*** Clones the vector and subtracts the scalar from it instead** @public* @chainable* @param {number} scalar The scalar to add to the vector* @return {Vector} Returns the clone of itself, modified*/subtractScalarNew(scalar) {let v = this.clone();return v.subtractScalar(scalar);}/*** Divides one vector by another.** @public* @chainable* @param {Vector} vector The vector to divide this by* @return {Vector} Returns itself, modified*/divide(vector) {if(vector.x !== 0) {this.x /= vector.x} else {this.x = 0;}if(vector.y !== 0) {this.y /= vector.y} else {this.y = 0;}return this;}/*** Clones the vector and divides it by the vector instead** @public* @chainable* @param {Vector} vector The vector to divide the clone by* @return {Vector} Returns the clone of itself, modified*/divideNew(vector) {let v = this.clone();return v.divide(vector);}/*** Divides the vector by a scalar.** @public* @chainable* @param {number} scalar The scalar to divide both x and y by* @return {Vector} Returns itself, modified*/divideScalar(scalar) {var v = new Vector(scalar, scalar);return this.divide(v);}/*** Clones the vector and divides it by the provided scalar.** @public* @chainable* @param {number} scalar The scalar to divide both x and y by* @return {Vector} Returns the clone of itself, modified*/divideScalarNew(scalar) {let v = this.clone();return v.divideScalar(scalar);}/*** Multiplies one vector by another.** @public* @chainable* @param {Vector} vector The vector to multiply this by* @return {Vector} Returns itself, modified*/multiply(vector) {this.x *= vector.x;this.y *= vector.y;return this;}/*** Clones the vector and multiplies it by the vector instead** @public* @chainable* @param {Vector} vector The vector to multiply the clone by* @return {Vector} Returns the clone of itself, modified*/multiplyNew(vector) {let v = this.clone();return v.multiply(vector);}/*** Multiplies the vector by a scalar.** @public* @chainable* @param {number} scalar The scalar to multiply both x and y by* @return {Vector} Returns itself, modified*/multiplyScalar(scalar) {var v = new Vector(scalar, scalar);return this.multiply(v);}/*** Clones the vector and multiplies it by the provided scalar.** @public* @chainable* @param {number} scalar The scalar to multiply both x and y by* @return {Vector} Returns the clone of itself, modified*/multiplyScalarNew(scalar) {let v = this.clone();return v.multiplyScalar(scalar);}/*** Alias of {@link Vector#multiplyScalar__anchor multiplyScalar}*/scale(scalar) {return this.multiplyScalar(scalar);}/*** Alias of {@link Vector#multiplyScalarNew__anchor multiplyScalarNew}*/scaleNew(scalar) {return this.multiplyScalarNew(scalar);}/*** Rotates a vecor by a given amount, provided in radians.** @public* @chainable* @param {number} radian The angle, in radians, to rotate the vector by* @return {Vector} Returns itself, modified*/rotate(radian) {var x = (this.x * Math.cos(radian)) - (this.y * Math.sin(radian));var y = (this.x * Math.sin(radian)) + (this.y * Math.cos(radian));this.x = x;this.y = y;return this;}/*** Clones the vector and rotates it by the supplied radian value** @public* @chainable* @param {number} radian The angle, in radians, to rotate the vector by* @return {Vector} Returns the clone of itself, modified*/rotateNew(radian) {let v = this.clone();return v.rotate(radian);}/*** Rotates a vecor by a given amount, provided in degrees. Converts the degree* value to radians and runs the rotaet method.** @public* @chainable* @param {number} degrees The angle, in degrees, to rotate the vector by* @return {Vector} Returns itself, modified*/rotateDeg(degrees) {return this.rotate(degreesToRadian(degrees));}/*** Clones the vector and rotates it by the supplied degree value** @public* @chainable* @param {number} degrees The angle, in degrees, to rotate the vector by* @return {Vector} Returns the clone of itself, modified*/rotateDegNew(degrees) {return this.rotateNew(degreesToRadian(degrees));}/*** Alias of {@link Vector#rotate__anchor rotate}*/rotateBy(radian) {return this.rotate(radian);}/*** Alias of {@link Vector#rotateNew__anchor rotateNew}*/rotateByNew(radian) {return this.rotateNew(radian);}/*** Alias of {@link Vector#rotateDeg__anchor rotateDeg}*/rotateDegBy(degrees) {return this.rotateDeg(degrees);}/*** Alias of {@link Vector#rotateDegNew__anchor rotateDegNew}*/rotateDegByNew(radian) {return tjos.rotateDegNew(radian);}/*** Rotates a vector to a specific angle** @public* @chainable* @param {number} radian The angle, in radians, to rotate the vector to* @return {Vector} Returns itself, modified*/rotateTo(radian) {return this.rotate(radian-this.angle);};/*** Clones the vector and rotates it to the supplied radian value** @public* @chainable* @param {number} radian The angle, in radians, to rotate the vector to* @return {Vector} Returns the clone of itself, modified*/rotateToNew(radian) {let v = this.clone();return v.rotateTo(radian);};/*** Rotates a vecor to a given amount, provided in degrees. Converts the degree* value to radians and runs the rotateTo method.** @public* @chainable* @param {number} degrees The angle, in degrees, to rotate the vector to* @return {Vector} Returns itself, modified*/rotateToDeg(degrees) {return this.rotateTo(degreesToRadian(degrees));}/*** Clones the vector and rotates it to the supplied degree value** @public* @chainable* @param {number} degrees The angle, in degrees, to rotate the vector to* @return {Vector} Returns the clone of itself, modified*/rotateToDegNew(degrees) {return this.rotateToNew(degreesToRadian(degrees));}/*** Normalises the vector down to a length of 1 unit** @public* @chainable* @return {Vector} Returns itself, modified*/normalise() {return this.divideScalar(this.length);}/*** Clones the vector and normalises it** @public* @chainable* @return {Vector} Returns a clone of itself, modified*/normaliseNew() {return this.divideScalarNew(this.length);}/*** Calculates the distance between this and the supplied vector** @param {Vector} vector The vector to calculate the distance from* @return {number} The distance between this and the supplied vector*/distance(vector) {return this.subtractNew(vector).length;}/*** Calculates the distance on the X axis between this and the supplied vector** @param {Vector} vector The vector to calculate the distance from* @return {number} The distance, along the x axis, between this and the supplied vector*/distanceX(vector) {return this.x - vector.x;}/*** Calculated the distance on the Y axis between this and the supplied vector** @param {Vector} vector The vector to calculate the distance from* @return {number} The distance, along the y axis, between this and the supplied vector*/distanceY(vector) {return this.y - vector.y;}/*** Calculates the dot product between this and a supplied vector** @example* // returns -14* new Vector(2, -3).dot(new Vector(-4, 2))* new Vector(-4, 2).dot(new Vector(2, -3))* new Vector(2, -4).dot(new Vector(-3, 2))** @param {Vector} vector The vector object against which to calculate the dot product* @return {number} The dot product of the two vectors*/dot(vector) {return (this.x * vector.x) + (this.y * vector.y);}/*** Calculates the cross product between this and the supplied vector.** @example* // returns -2* new Vector(2, -3).cross(new Vector(-4, 2))* new Vector(-4, 2).cross(new Vector(2, -3))* // returns 2* new Vector(2, -4).cross(new Vector(-3, 2))** @param {Vector} vector The vector object against which to calculate the cross product* @return {number} The cross product of the two vectors*/cross(vector) {return (this.x * vector.x) - (this.y * vector.y);}// TODO Add this to the main classisEqualTo(vector) {return this.x === vector.x && this.y === vector.y;}// TODO Add this to the main classslopeOf(vector) {return ( vector.y - this.y ) / ( vector.x - this.x );}/*** Getters and setters*//*** (getter/setter) The x value of the vector.** @type {number}* @default 0*/set x(x) {if(typeof x == 'number') {this._x = x;} else {throw new TypeError('X should be a number');}}get x() {return this._x || 0;}/*** (getter/setter) The y value of the vector.** @type {number}* @default 0*/set y(y) {if(typeof y == 'number') {this._y = y;} else {throw new TypeError('Y should be a number');}}get y() {return this._y || 0;}/*** (getter/setter) The length of the vector presented as a square. If you're using* length for comparison, this is quicker.** @type {number}* @default 0*/set lengthSquared(length) {var factor;if(typeof length == 'number') {factor = length / this.lengthSquared;this.multiplyScalar(factor);} else {throw new TypeError('length should be a number');}}get lengthSquared() {return (this.x * this.x) + (this.y * this.y);}/*** (getter/setter) The length of the vector** @type {number}* @default 0*/set length(length) {var factor;if(typeof length == 'number') {factor = length / this.length;this.multiplyScalar(factor);} else {throw new TypeError('length should be a number');}}get length() {return Math.sqrt(this.lengthSquared);}/*** (getter/setter) The angle of the vector, in radians** @type {number}* @default 0*/set angle(radian) {if(typeof radian == 'number') {this.rotateTo(radian);} else {throw new TypeError('angle should be a number');}}get angle() {return Math.atan2(this.y, this.x);}/*** (getter/setter) The angle of the vector, in degrees** @type {number}* @default 0*/set angleInDegrees(degrees) {if(typeof degrees == 'number') {this.rotateToDeg(degrees);} else {throw new TypeError('angle should be a number');}}get angleInDegrees() {return radianToDegrees(Math.atan2(this.y, this.x));}/*** (getter/setter) Vector width.* Alias of {@link Vector#x x}** @type {number}*/set width(w) {this.x = w;}get width() {return this.x;}/*** (getter/setter) Vector height.* Alias of {@link Vector#x x}** @type {number}*/set height(h) {this.y = h;}get height() {return this.y;}/*** (getter/setter) Vector area.* @readonly** @type {number}*/get area() {return this.x * this.y;}// TODO Add this to the main classget slope() {return this.y / this.x;}}