生长自由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