生长自由js动画效果
代码语言:html
所属分类:动画
下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title> Freedom 4</title> <style> body { background: black; } canvas.drawer { position:fixed; top: 0px; left: 0px; width: 100vw; height: 100vh; } </style> </head> <body translate="no"> <script> // Liam Egan // 2019 let initialise = function () { let application = new Application(); // application.scaleFactor = 2; // application.clearOnRedraw = Application.FADE; application.fadeColour = 'rgba(0,0,0,.12)'; application.fillColour = 'rgba(30,30,30,1)'; application.onResize(); let vfield = new VectorField(); vfield.scale = 2000; vfield.amplitude = 10; // vfield.debug = true; application.addActor(vfield); let maxNum = 1000; let num = 0; let addTracer = (position, colour) => { if (num > maxNum) return; let tracer = new BranchTracer(position.x, position.y); tracer.field = vfield; let momentum = new Vector(Math.random(), Math.random()); momentum.length = Math.random() * 2; tracer.momentum = momentum; tracer.friction = 0.97; if (colour) { tracer.colour = colour; } else { tracer.colour = 'RGBA(' + 100 + Math.round(Math.random() * 155) + ',' + 100 + Math.round(Math.random() * 155) + ',255,0.5)'; } application.addActor(tracer); num = application.actors.length; return tracer; }; let seed = addTracer(new Vector(window.innerWidth / 2, window.innerHeight / 2), 'RGBA(255, 100, 100, 0.5)'); seed.initial = true; seed.branchChance = 15; seed.friction = 0.9975; seed.onBranch = addTracer; // setInterval(()=> { // vfield.z = Math.random() * 10000; // }, 10000) let stage = application.stage; document.body.appendChild(stage); application.onPointerMove({ clientX: window.innerWidth / 2, clientY: window.innerHeight / 2 }); application.render(); application.animating = true; // application.runFor(60 * 120); return; }; class Application { constructor() { this.stage = document.createElement('canvas'); this.animate = this.animate.bind(this); this.onResize = this.onResize.bind(this); this.onPointerDown = this.onPointerDown.bind(this); this.onPointerup = this.onPointerup.bind(this); this.onPointerMove = this.onPointerMove.bind(this); this.initialiseEvents(); } initialiseEvents() { window.addEventListener('resize', this.onResize, false); document.addEventListener('pointerdown', this.onPointerDown, false); document.addEventListener('pointerup', this.onPointerup, false); document.addEventListener('pointermove', this.onPointerMove, false); } deInitialiseEvents() { window.removeEventListener('resize', this.onResize, false); document.removeEventListener('pointerdown', this.onPointerDown, false); document.removeEventListener('pointerup', this.onPointerup, false); document.removeEventListener('pointermove', this.onPointerMove, false); } addActor(actor) { if (actor instanceof Actor) { this.actors.push(actor); } } runFor(ticks) { let interval = 1 / 60; let i = 0; for (i; i < ticks; i++) { this.triggerEvent('application-animate', { now: this.now, then: this.then, interval: interval, application: this }); this.render(); } } animate(delta) { this.now = Date.now(); let interval = this.now - this.then; this.triggerEvent('application-animate', { now: this.now, then: this.then, interval: interval, application: this }); this.render(delta); this.then = this.now; // remove dead actors if (delta - this.removalTest > 5000 || !this.removalTest) { this.removalTest = delta; for (let i = this.actors.length - 1; i >= 0; i--) { if (this.actors[i].dead === true) { this.actors.splice(i, 1); } } } if (this.animating) { requestAnimationFrame(this.animate); } } render(delta) { let dims = this.dimensions; if (this.animatingOut) { this.opacity += .02; if (this.opacity >= 1) this.animatingOut = false; this.context.fillStyle = `rgba(10,10,10,${this.opacity}`; this.context.fillRect(0, 0, dims.width, dims.height); } if (this.clearOnRedraw == Application.CLEAR) { this.context.clearRect(0, 0, dims.width, dims.height); } else if (this.clearOnRedraw == Application.FADE) { this.context.fillStyle = this.fadeColour; this.context.fillRect(0, 0, dims.width, dims.height); } this.actors.forEach(actor => { actor.render(this); }); } onResize(e) { this.dimensions = new Vector(window.innerWidth * 2, window.innerHeight * 2); } onPointerDown(e) { } onPointerup(e) { } onPointerMove(e) { let pointer = new Vector(e.clientX, e.clientY); this.triggerEvent('application-pointermove', { pointer: pointer }); } triggerEvent(event, data) { if (window.CustomEvent) { var event = new CustomEvent(event, { detail: data }); } else { var event = document.createEvent('CustomEvent'); event.initCustomEvent(event, true, true, data); } document.dispatchEvent(event); } get actors() { if (!this._actors) this._actors = []; return this._actors; } set scaleFactor(value) { if (value >= 1) { this._scaleFactor = value; this.onResize(); } } get scaleFactor() { return this._scaleFactor || 1; } set dimensions(value) { if (value instanceof Vector) { value.scale(this.scaleFactor); this.stage.width = value.width; this.stage.height = value.height; this.context.fillStyle = this.fillColour; this.context.fillRect(0, 0, this.dimensions.width, this.dimensions.height); this._dimensions = value; } } get dimensions() { return this._dimensions || new Vector(0, 0); } set stage(value) { if (value instanceof HTMLCanvasElement) { value.className = this.className; this._stage = value; this.context = this.stage.getContext('2d'); this.context.fillStyle = this.fillColour; this.context.fillRect(0, 0, this.dimensions.width, this.dimensions.height); this.onResize(); } } get stage() { return this._stage || null; } set now(value) { if (!isNaN(value)) this._now = value; } get now() { return this._now || 0; } set then(value) { if (!isNaN(value)) this._then = value; } get then() { return this._then || 0; } set animating(value) { if (value === true && this.animating !== true) { this._animating = true; this.now = Date.now(); this.then = this.now; requestAnimationFrame(this.animate); } } get animating() { return this._animating === true; } set fadeColour(value) { this._fadeColour = value; } get fadeColour() { return this._fadeColour || 'rgba(255,255,255,.5)'; } set fillColour(value) { this._fillColour = value; } get fillColour() { return this._fillColour || 'rgba(255,255,255,1)'; } set clearOnRedraw(value) { if ([Application.NOCLEAR, Application.CLEAR, Application.FADE].indexOf(value) > -1) { this._clearOnRedraw = value; } } get clearOnRedraw() { return this._clearOnRedraw || Application.NOCLEAR; } get className() { return 'drawer'; }} Application.NOCLEAR = 0; Application.CLEAR = 1; Application.FADE = 2; class Actor { constructor(x, y, w, h) { this.dimensions = new Vector(w, h); this.position = new Vector(x, y); } render() { } set dimensions(value) { if (value instanceof Vector) this._dimensions = value; } get dimensions() { return this._dimensions || new Vector(0, 0); } set position(value) { if (value instanceof Vector) this._position = value; } get position() { return this._position || new Vector(0, 0); }} class VectorField extends Actor { constructor(x = 0, y = 0, w = 0, h = 0) { super(x, y, w, h); this.noise = new Noise(); this.helpers = []; this.mousepos = new Vector(0, 0); this.onResize = this.onResize.bind(this); this.onPointerMove = this.onPointerMove.bind(this); // document.addEventListener('application-pointermove', this.onPointerMove, false); window.addEventListener('resize', this.onResize); this.onResize(); } render(application) { this.helpers.forEach(helper => { helper.render(application); }); } preDraw() {} postDraw() {} solveForPosition(v) { if (!v instanceof Vector) return; v = v.clone(); v.x -= window.innerWidth / 2; v.y -= window.innerHeight / 2; let scale = this.scale; let amp = this.amplitude; // let waveform = new Vector(Math.cos(v.x / scale) * amp, Math.sin(v.y / scale) * amp); // return new Vector(waveform.y - waveform.x, -waveform.x - waveform.y); let envelope = this.amplitude; let noise = this.noise.noise(v.x / scale, v.y / scale, this.z) * scale; if (noise > envelope) noise = envelope; if (noise < -envelope) noise = -envelope; let noise1 = this.noise.noise(v.y / scale, v.x / scale, this.z / scale); let transV = new Vector(1, 0); transV.length = noise; transV.angle = noise1 * 10; return transV; let transv = v.subtractNew(this.mousepos); transv = new Vector(transv.y - transv.x, -transv.x - transv.y); transv.length *= 0.03; if (transv.length > 50) { transv.length = 50; } transv.length -= 50; transv.length *= -1; return transv; } onPointerMove(e) { this.mousepos = e.detail.pointer; this.helpers.forEach(helper => { helper.vector = this.solveForPosition(helper.position); }); } onResize(e) { if (!this.debug) return; this.helpers.forEach(helper => { helper.destroy(); }); this.helpers = []; let w = this.sampleWidth; let curpos = new Vector(0, 0); while (curpos.y < window.innerHeight + w) { curpos.x = 0; while (curpos.x < window.innerWidth + w) { this.helpers.push(new Arrow(curpos.x, curpos.y, 10, 10, this.solveForPosition(curpos))); curpos.x += w; } curpos.y += w; } } set scale(value) { if (value > 0) { this._scale = value; } } get scale() { return this._scale || 500; } set amplitude(value) { if (value > 0) { this._amplitude = value; } } get amplitude() { return this._amplitude || 10; } set sampleWidth(value) { if (value > 0) this._sampleWidth = value; } get sampleWidth() { return this._sampleWidth || 30; } set z(value) { if (value > 0) this._z = value; } get z() { return this._z || 30; } set mousepos(value) { if (value instanceof Vector) this._mousepos = value; } get mousepos() { return this._mousepos || new Vector(0, 0); } set debug(value) { this._debug = value === true; } get debug() { return this._debug === true; } get strokeStyle() { return 'black'; } get strokeWidth() { return 0; }} class Tracer extends Actor { constructor(x = 200, y = 200, w = 40, h = 20) { super(x, y, w, h); this.onAnimate = this.onAnimate.bind(this); document.addEventListener('application-animate', this.onAnimate, false); this.friction = 0.95; this.momentum = new Vector(1, 0); } onAnimate(e) { let force = this.field.solveForPosition(this.position).multiplyScalar(0.01); let app = e.detail.application; let oldPosition = this.position.clone(); let draw = true; this.momentum.add(force); this.momentum.multiplyScalar(this.friction); if (this.momentum.length < 1) this.momentum.length = 1; if (this.momentum.length > 20) this.momentum.length = 20; this.position.add(this.momentum); if (this.position.x < -this.dimensions.width) { this.position.x = app.dimensions.width + this.dimensions.width; draw = false; } else if (this.position.x > app.dimensions.width + this.dimensions.width) { this.position.x = -this.dimensions.width; draw = false; } if (this.position.y < -this.dimensions.height) { this.position.y = app.dimensions.height + this.dimensions.height; draw = false; } else if (this.position.y > app.dimensions.height + this.dimensions.height) { this.position.y = -this.dimensions.height; draw = false; } if (draw) { let context = app.context; let opacity = Math.abs((this.momentum.length - 10) / 20); // console.log(opacity, this.momentum.length); // console.log(oldPosition, this.position); context.beginPath(); context.lineWidth = this.momentum.length * 2; context.strokeStyle = 'RGBA(0,0,0,0.2)'; context.moveTo(oldPosition.x, oldPosition.y); context.lineTo(this.position.x, this.position.y); context.stroke(); context.beginPath(); context.lineWidth = this.momentum.length; context.strokeStyle = this.colour; context.moveTo(oldPosition.x, oldPosition.y); context.lineTo(this.position.x, this.position.y); context.stroke(); } } set colour(value) { this._colour = value; } get colour() { return this._colour || 'RGBA(255,255,255,0.5)'; }} class BranchTracer extends Tracer { constructor(x = 200, y = 200, w = 40, h = 20) { super(x, y, w, h); this.originalPosition = new Vector(x, y); this.openTimer = 0.; this.dieTimer = .5; this.openRadius = 5. + Math.random() * 10.; this.startLife = Date.now(); this.lifeTime = 1000 + Math.random() * 15000; this.dying = false; this.dead = false; } drawDeath(app) { let context = app.context; // if(this.dieTimer == .5) { // context.beginPath(); // context.moveTo(this.deadPosition.x, this.deadPosition.y); // context.fillStyle = `rgba(20,20,20,.1)`; // context.arc(this.deadPosition.x, this.deadPosition.y, this.openRadius * 10., 0, 2 * Math.PI); // context.fill(); // } context.beginPath(); context.moveTo(this.deadPosition.x, this.deadPosition.y); context.fillStyle = `rgba(50,${this.dieTimer * 2.},${this.dieTimer * 2.},${this.dieTimer / 200.})`; context.arc(this.deadPosition.x, this.deadPosition.y, this.openRadius * 2. / 100 * (100. - this.dieTimer), 0, 2 * Math.PI); context.fill(); this.dieTimer += (100 - this.dieTimer) * .05; } onAnimate(e) { let app = e.detail.application; let context = app.context; if (this.dying) { if (this.dieTimer < 99.) { this.drawDeath(app); } else { this.dead = true; } return; } super.onAnimate(e); if (e.detail.now - this.startLife > this.lifeTime && !this.initial) { this.dying = true; this.deadPosition = this.position; } // console.log(e.detail.now) if (this.openTimer < 100.) { context.moveTo(this.originalPosition.x, this.originalPosition.y); context.fillStyle = 'rgba(0,0,0,.1)'; context.arc(this.originalPosition.x, this.originalPosition.y, this.openRadius / 100 * (this.openTimer + 10), 0, 2 * Math.PI); context.fill(); context.beginPath(); context.fillStyle = 'rgba(255,255,255,.4)'; context.arc(this.originalPosition.x, this.originalPosition.y, this.openRadius / 100 * this.openTimer, 0, 2 * Math.PI); context.fill(); this.openTimer += 5.; } if (Math.random() * 100 < this.branchChance) { this.onBranch(this.position); } } set onBranch(value) { if (typeof value == 'function') this._onBranch = value.bind(this); } get onBranch() { return this._onBranch || function () {}; } set branchChance(value) { if (value > 0 && value <= 100) this._branchChance = value; } get branchChance() { return this._branchChance || 0.2; }} 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 length this.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 point var X = Math.floor(x); var Y = Math.floor(y); var Z = Math.floor(z); // Get relative xyz coordinates of point within that cell x = 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 indices var 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.per.........完整代码请登录后点击上方下载按钮下载查看
网友评论0