Canvas游动的花花肠子

时间:2022-07-25
本文章向大家介绍Canvas游动的花花肠子,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

Canvas顶级动画之游动的花花肠子

代码比较精简,适合有一定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]%12}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]]]]%32*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]]]]%32*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]]]]%32*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]]]]%32*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]]]]%32*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>

代码量不是很多,但效果没的说。如果你能把这个用作网页背景图,相信找工作不是难题,学习没有捷径,“ 不积跬步无以至千里 ” 。