Canvas顶级动画之游动的花花肠子
代码语言:javascript复制代码比较精简,适合有一定Canvas基础的程序员开发,如果你没有基础,请点击它:一看就学废,“我不是费圆” 博客指南
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
html, body {
overflow: hidden;
background: black;
padding:0;
margin:0;
}
</style>
</head>
<body>
</body>
<script>
"use strict";
const {
PI,
cos,
sin,
tan,
abs,
sqrt,
pow,
min,
max,
ceil,
floor,
round,
random,
atan2
} = Math;
const HALF_PI = 0.5 * PI;
const QUART_PI = 0.25 * PI;
const TAU = 2 * PI;
const TO_RAD = PI / 180;
const G = 6.67 * pow(10, -11);
const EPSILON = 2.220446049250313e-16;
const rand = n => n * random();
const randIn = (_min, _max) => rand(_max - _min) _min;
const randRange = n => n - rand(2 * n);
const fadeIn = (t, m) => t / m;
const fadeOut = (t, m) => (m - t) / m;
const fadeInOut = (t, m) => {
let hm = 0.5 * m;
return abs((t hm) % m - hm) / hm;
};
const dist = (x1, y1, x2, y2) => sqrt(pow(x2 - x1, 2) pow(y2 - y1, 2));
const angle = (x1, y1, x2, y2) => atan2(y2 - y1, x2 - x1);
const lerp = (a, b, amt) => (1 - amt) * a amt * b;
const vh = p => p * window.innerHeight * 0.01;
const vw = p => p * window.innerWidth * 0.01;
const vmin = p => min(vh(p), vw(p));
const vmax = p => max(vh(p), vw(p));
const clamp = (n, _min, _max) => min(max(n, _min), _max);
const norm = (n, _min, _max) => (n - _min) / (_max - _min);
Array.prototype.lerp = function(t = [], a = 0) {
this.forEach((n, i) => (this[i] = lerp(n, t[i], a)));
};
Float32Array.prototype.get = function(i = 0, n = 0) {
const t = i n;
let r = [];
for (; i < t; i ) {
r.push(this[i]);
}
return r;
};
class PropsArray {
constructor(count = 0, props = []) {
this.count = count;
this.props = props;
this.values = new Float32Array(count * props.length);
}
get length() {
return this.values.length;
}
set(a = [], i = 0) {
this.values.set(a, i);
}
setMap(o = {}, i = 0) {
this.set(Object.values(o), i);
}
get(i = 0) {
return this.values.get(i, this.props.length);
}
getMap(i = 0) {
return this.get(i).reduce(
(r, v, i) => ({
...r,
...{ [this.props[i]]: v }
}),
{}
);
}
forEach(cb) {
let i = 0;
for (; i < this.length; i = this.props.length) {
cb(this.get.call(this, i), i, this);
}
}
map(cb) {
let i = 0;
for (; i < this.length; i = this.props.length) {
this.set(cb(this.get.call(this, i), i, this), i);
}
}
}
!function(){"use strict";var r=.5*(Math.sqrt(3)-1),e=(3-Math.sqrt(3))/6,t=1/6,a=(Math.sqrt(5)-1)/4,o=(5-Math.sqrt(5))/20;function i(r){var e;e="function"==typeof r?r:r?function(){var r=0,e=0,t=0,a=1,o=(i=4022871197,function(r){r=r.toString();for(var e=0;e<r.length;e ){var t=.02519603282416938*(i =r.charCodeAt(e));t-=i=t>>>0,i=(t*=i)>>>0,i =4294967296*(t-=i)}return 2.3283064365386963e-10*(i>>>0)});var i;r=o(" "),e=o(" "),t=o(" ");for(var n=0;n<arguments.length;n )(r-=o(arguments[n]))<0&&(r =1),(e-=o(arguments[n]))<0&&(e =1),(t-=o(arguments[n]))<0&&(t =1);return o=null,function(){var o=2091639*r 2.3283064365386963e-10*a;return r=e,e=t,t=o-(a=0|o)}}(r):Math.random,this.p=n(e),this.perm=new Uint8Array(512),this.permMod12=new Uint8Array(512);for(var t=0;t<512;t )this.perm[t]=this.p[255&t],this.permMod12[t]=this.perm[t]}function n(r){var e,t=new Uint8Array(256);for(e=0;e<256;e )t[e]=e;for(e=0;e<255;e ){var a=e ~~(r()*(256-e)),o=t[e];t[e]=t[a],t[a]=o}return t}i.prototype={grad3:new Float32Array([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]),grad4:new Float32Array([0,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,1,0,1,1,1,0,1,-1,1,0,-1,1,1,0,-1,-1,-1,0,1,1,-1,0,1,-1,-1,0,-1,1,-1,0,-1,-1,1,1,0,1,1,1,0,-1,1,-1,0,1,1,-1,0,-1,-1,1,0,1,-1,1,0,-1,-1,-1,0,1,-1,-1,0,-1,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,0]),noise2D:function(t,a){var o,i,n=this.permMod12,f=this.perm,s=this.grad3,v=0,h=0,l=0,u=(t a)*r,d=Math.floor(t u),p=Math.floor(a u),M=(d p)*e,m=t-(d-M),c=a-(p-M);m>c?(o=1,i=0):(o=0,i=1);var y=m-o e,w=c-i e,g=m-1 2*e,A=c-1 2*e,x=255&d,q=255&p,D=.5-m*m-c*c;if(D>=0){var S=3*n[x f[q]];v=(D*=D)*D*(s[S]*m s[S 1]*c)}var U=.5-y*y-w*w;if(U>=0){var b=3*n[x o f[q i]];h=(U*=U)*U*(s[b]*y s[b 1]*w)}var F=.5-g*g-A*A;if(F>=0){var N=3*n[x 1 f[q 1]];l=(F*=F)*F*(s[N]*g s[N 1]*A)}return 70*(v h l)},noise3D:function(r,e,a){var o,i,n,f,s,v,h,l,u,d,p=this.permMod12,M=this.perm,m=this.grad3,c=(r e a)*(1/3),y=Math.floor(r c),w=Math.floor(e c),g=Math.floor(a c),A=(y w g)*t,x=r-(y-A),q=e-(w-A),D=a-(g-A);x>=q?q>=D?(s=1,v=0,h=0,l=1,u=1,d=0):x>=D?(s=1,v=0,h=0,l=1,u=0,d=1):(s=0,v=0,h=1,l=1,u=0,d=1):q<D?(s=0,v=0,h=1,l=0,u=1,d=1):x<D?(s=0,v=1,h=0,l=0,u=1,d=1):(s=0,v=1,h=0,l=1,u=1,d=0);var S=x-s t,U=q-v t,b=D-h t,F=x-l 2*t,N=q-u 2*t,C=D-d 2*t,P=x-1 .5,T=q-1 .5,_=D-1 .5,j=255&y,k=255&w,z=255&g,B=.6-x*x-q*q-D*D;if(B<0)o=0;else{var E=3*p[j M[k M[z]]];o=(B*=B)*B*(m[E]*x m[E 1]*q m[E 2]*D)}var G=.6-S*S-U*U-b*b;if(G<0)i=0;else{var H=3*p[j s M[k v M[z h]]];i=(G*=G)*G*(m[H]*S m[H 1]*U m[H 2]*b)}var I=.6-F*F-N*N-C*C;if(I<0)n=0;else{var J=3*p[j l M[k u M[z d]]];n=(I*=I)*I*(m[J]*F m[J 1]*N m[J 2]*C)}var K=.6-P*P-T*T-_*_;if(K<0)f=0;else{var L=3*p[j 1 M[k 1 M[z 1]]];f=(K*=K)*K*(m[L]*P m[L 1]*T m[L 2]*_)}return 32*(o i n f)},noise4D:function(r,e,t,i){var n,f,s,v,h,l,u,d,p,M,m,c,y,w,g,A,x,q=this.perm,D=this.grad4,S=(r e t i)*a,U=Math.floor(r S),b=Math.floor(e S),F=Math.floor(t S),N=Math.floor(i S),C=(U b F N)*o,P=r-(U-C),T=e-(b-C),_=t-(F-C),j=i-(N-C),k=0,z=0,B=0,E=0;P>T?k :z ,P>_?k :B ,P>j?k :E ,T>_?z :B ,T>j?z :E ,_>j?B :E ;var G=P-(l=k>=3?1:0) o,H=T-(u=z>=3?1:0) o,I=_-(d=B>=3?1:0) o,J=j-(p=E>=3?1:0) o,K=P-(M=k>=2?1:0) 2*o,L=T-(m=z>=2?1:0) 2*o,O=_-(c=B>=2?1:0) 2*o,Q=j-(y=E>=2?1:0) 2*o,R=P-(w=k>=1?1:0) 3*o,V=T-(g=z>=1?1:0) 3*o,W=_-(A=B>=1?1:0) 3*o,X=j-(x=E>=1?1:0) 3*o,Y=P-1 4*o,Z=T-1 4*o,$=_-1 4*o,rr=j-1 4*o,er=255&U,tr=255&b,ar=255&F,or=255&N,ir=.6-P*P-T*T-_*_-j*j;if(ir<0)n=0;else{var nr=q[er q[tr q[ar q[or]]]]2*4;n=(ir*=ir)*ir*(D[nr]*P D[nr 1]*T D[nr 2]*_ D[nr 3]*j)}var fr=.6-G*G-H*H-I*I-J*J;if(fr<0)f=0;else{var sr=q[er l q[tr u q[ar d q[or p]]]]2*4;f=(fr*=fr)*fr*(D[sr]*G D[sr 1]*H D[sr 2]*I D[sr 3]*J)}var vr=.6-K*K-L*L-O*O-Q*Q;if(vr<0)s=0;else{var hr=q[er M q[tr m q[ar c q[or y]]]]2*4;s=(vr*=vr)*vr*(D[hr]*K D[hr 1]*L D[hr 2]*O D[hr 3]*Q)}var lr=.6-R*R-V*V-W*W-X*X;if(lr<0)v=0;else{var ur=q[er w q[tr g q[ar A q[or x]]]]2*4;v=(lr*=lr)*lr*(D[ur]*R D[ur 1]*V D[ur 2]*W D[ur 3]*X)}var dr=.6-Y*Y-Z*Z-$*$-rr*rr;if(dr<0)h=0;else{var pr=q[er 1 q[tr 1 q[ar 1 q[or 1]]]]2*4;h=(dr*=dr)*dr*(D[pr]*Y D[pr 1]*Z D[pr 2]*$ D[pr 3]*rr)}return 27*(n f s v h)}},i._buildPermutationTable=n,"undefined"!=typeof define&&define.amd&&define(function(){return i}),"undefined"!=typeof exports?exports.SimplexNoise=i:"undefined"!=typeof window&&(window.SimplexNoise=i),"undefined"!=typeof module&&(module.exports=i)}();
"use strict";
const backgroundColor = "rgba(0,0,10,0.5)";
const baseHue = rand(360);
const rangeHue = 180;
const tentacleCount = 30;
const segmentCountMin = 10;
const segmentCountMax = 20;
const segmentLengthMin = 20;
const segmentLengthMax = 40;
const colonyRadius = 200;
let canvas;
let ctx;
let center;
let mouse;
let tick;
let simplex;
let tentacle;
let tentacles;
class Tentacle {
constructor(x, y, segmentNum, baseLength, baseDirection) {
this.base = [x, y];
this.position = [x, y];
this.target = [x, y];
this.segmentNum = segmentNum;
this.baseLength = baseLength;
this.baseDirection = baseDirection;
this.segmentProps = ["x1", "y1", "x2", "y2", "l", "d", "h"];
this.segments = new PropsArray(segmentNum, this.segmentProps);
this.follow = false;
let i = this.segments.length - this.segmentProps.length;
let x1, y1, x2, y2, l, d, h;
l = this.baseLength;
d = this.baseDirection;
for (; i >= 0; i -= this.segmentProps.length) {
x1 = x2 || this.position[0];
y1 = y2 || this.position[1];
x2 = x1 - l * cos(d);
y2 = y1 - l * sin(d);
d = 0.309;
l *= 0.98;
h = baseHue fadeIn(i, this.segments.length) * rangeHue;
this.segments.set([x1, y1, x2, y2, l, d, h], i);
}
}
setCtx(ctx) {
this.ctx = ctx;
}
setTarget(target) {
this.target = target;
}
updateBase() {
let t = simplex.noise3D(this.base[0] * 0.005, this.base[1] * 0.005, tick * 0.005) * TAU;
this.base.lerp([
this.base[0] 20 * cos(t),
this.base[1] 20 * sin(t)],
0.025);
}
async update() {
let target = this.position;
let i = this.segments.length - this.segmentProps.length;
let promises = [];
this.position.lerp(this.target, 0.015);
!this.follow && this.updateBase();
for (; i >= 0; i -= this.segmentProps.length) {
promises.push(
new Promise(resolve => {
let [x1, y1, x2, y2, l, d, h] = this.segments.get(i);
let t, n, tn;
x1 = target[0];
y1 = target[1];
t = angle(x1, y1, x2, y2);
n = simplex.noise3D(
x1 * 0.005,
y1 * 0.005,
(i tick) * 0.005);
tn = t n * PI * 0.0125;
x2 = x1 l * cos(tn);
y2 = y1 l * sin(tn);
d = t;
target = [x2, y2];
this.segments.set([x1, y1, x2, y2, l, d], i);
this.drawSegment(x1, y1, x2, y2, h, n, i);
resolve();
}));
}
await Promise.all(promises);
}
drawSegment(x1, y1, x2, y2, h, n, i) {
const fn = fadeInOut(1 n, 2);
const fa = fadeInOut(i, this.segments.length);
const a = 0.25 * (fn fa);
this.ctx.beginPath();
this.ctx.strokeStyle = `hsla(${h}, 50%, 50%, ${a})`;
this.ctx.moveTo(x2, y2);
this.ctx.lineTo(x1, y1);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.closePath();
this.ctx.strokeStyle = `hsla(${h}, 50%, 50%, ${a 0.5})`;
this.ctx.arc(x1, y1, fn * 3, 0, TAU);
this.ctx.stroke();
this.ctx.closePath();
}}
function setup() {
tick = 0;
simplex = new SimplexNoise();
mouse = [0, 0];
createCanvas();
resize();
tentacles = [];
let i, t;
for (i = 0; i < tentacleCount; i ) {
t = i / tentacleCount * TAU;
tentacle = new Tentacle(
center[0] colonyRadius * cos(rand(TAU)),
center[1] colonyRadius * sin(rand(TAU)),
round(randIn(segmentCountMin, segmentCountMax)),
round(randIn(segmentLengthMin, segmentLengthMax)),
t);
tentacle.setCtx(ctx.a);
tentacles.push(tentacle);
}
loop();
}
function createCanvas() {
canvas = {
a: document.createElement("canvas"),
b: document.createElement("canvas") };
canvas.b.style = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
`;
document.body.appendChild(canvas.b);
ctx = {
a: canvas.a.getContext("2d"),
b: canvas.b.getContext("2d") };
center = [0.5 * canvas.a.width, 0.5 * canvas.a.height];
}
function resize() {
const { innerWidth, innerHeight } = window;
canvas.a.width = innerWidth;
canvas.a.height = innerHeight;
ctx.a.drawImage(canvas.b, 0, 0);
canvas.b.width = innerWidth;
canvas.b.height = innerHeight;
ctx.b.drawImage(canvas.a, 0, 0);
center[0] = 0.5 * canvas.a.width;
center[1] = 0.5 * canvas.a.height;
}
function renderBackground() {
ctx.a.clearRect(0, 0, canvas.b.width, canvas.b.height);
ctx.b.fillStyle = backgroundColor;
ctx.b.fillRect(0, 0, canvas.a.width, canvas.a.height);
}
function renderGlow() {
ctx.b.save();
ctx.b.filter = "blur(8px) brightness(200%)";
ctx.b.globalCompositeOperation = "lighter";
ctx.b.drawImage(canvas.a, 0, 0);
ctx.b.restore();
ctx.b.save();
ctx.b.filter = "blur(4px) brightness(200%)";
ctx.b.globalCompositeOperation = "lighter";
ctx.b.drawImage(canvas.a, 0, 0);
ctx.b.restore();
}
function renderToScreen() {
ctx.b.save();
ctx.b.globalCompositeOperation = "lighter";
ctx.b.drawImage(canvas.a, 0, 0);
ctx.b.restore();
}
async function loop() {
tick ;
renderBackground();
await Promise.all(tentacles.map(tentacle => tentacle.update()));
renderGlow();
renderToScreen();
window.requestAnimationFrame(loop);
}
window.addEventListener("load", setup);
window.addEventListener("resize", resize);
window.addEventListener("mousemove", e => {
tentacles.forEach((tentacle, i) => {
const t = i / tentacles.length * TAU;
tentacle.setTarget([e.clientX colonyRadius * cos(t tick * 0.05), e.clientY colonyRadius * sin(t tick * 0.05)]);
tentacle.follow = true;
});
});
window.addEventListener("mouseout", () => {
tentacles.forEach(tentacle => {
tentacle.base = [
center[0] colonyRadius * cos(rand(TAU)),
center[1] colonyRadius * sin(rand(TAU))];
tentacle.setTarget(tentacle.base);
tentacle.follow = false;
});
});
</script>
</html>
代码量不是很多,但效果没的说。如果你能把这个用作网页背景图,相信找工作不是难题,学习没有捷径,“ 不积跬步无以至千里 ” 。