canvas妖风线条动画效果代码

代码语言:html

所属分类:动画

代码描述:canvas妖风线条动画效果代码

代码标签: canvas 妖风 线条 动画

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>
<html lang="en" >

<head>
  <meta charset="UTF-8">
  
  
  
<style>
body {
  font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
  background-color: #000;
  margin: 0;
  padding: 0;
  border-width: 0;
  overflow: hidden;
  cursor: pointer;
}
</style>


  
  
</head>

<body translate="no">
  <!--
vertical mouse position controls pattern size
horizontal mouse position controls animation speed
-->
  
      <script  >
"use strict";

let canv, ctx; // canvas and context
let maxx, maxy; // canvas dimensions

let radius; // hexagons radius (and side length)
let grid; // array of hexagons
let nbx, nby; // grid size (in elements, not pixels)
let orgx, orgy;
let perx, pery, pergrid;
let speed = 1; // pixels / ms
let bg;
let relY = 0.5;

// to create a non linear (linear by ranges) relationship between mouse position and speed
const POSSPEED = [0, 0.05, 0.5, 0.8, 0.94, 0.95, 1.0];
const VALSPEED = [0, 0, 1, 5, 100, 1000, 1000];

let nbLines, trackWidth, relTrackWidth;
let lines;

// for animation
let messages;

// shortcuts for Math.
const mrandom = Math.random;
const mfloor = Math.floor;
const mround = Math.round;
const mceil = Math.ceil;
const mabs = Math.abs;
const mmin = Math.min;
const mmax = Math.max;

const mPI = Math.PI;
const mPIS2 = Math.PI / 2;
const mPIS3 = Math.PI / 3;
const m2PI = Math.PI * 2;
const m2PIS3 = Math.PI * 2 / 3;
const msin = Math.sin;
const mcos = Math.cos;
const matan2 = Math.atan2;

const mhypot = Math.hypot;
const msqrt = Math.sqrt;

const rac3 = msqrt(3);
const rac3s2 = rac3 / 2;

//------------------------------------------------------------------------

function alea(mini, maxi) {
  // random number in given range

  if (typeof maxi == "undefined") return mini * mrandom(); // range 0..mini

  return mini + mrandom() * (maxi - mini); // range mini..maxi
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function intAlea(mini, maxi) {
  // random integer in given range (mini..maxi - 1 or 0..mini - 1)
  //
  if (typeof maxi == "undefined") return mfloor(mini * mrandom()); // range 0..mini - 1
  return mini + mfloor(mrandom() * (maxi - mini)); // range mini .. maxi - 1
}

function lerp(p0, p1, alpha) {
  return {
    x: p0.x * (1 - alpha) + p1.x * alpha,
    y: p0.y * (1 - alpha) + p1.y * alpha };

}

//------------------------------------------------------------------------

class Hexagon {
  constructor(kx, ky) {
    this.kx = kx;
    this.ky = ky;
    //        this.rot = intAlea(6); // random orientation
    this.rot = pergrid[ky % pery][kx % perx];

    this.exits = [];
    this.arcType = [];
    for (let k = 0; k < 6; ++k) {
      let v = [5, null, 0, 2, null, 3][(k - this.rot + 6) % 6];
      if (v === null) this.exits[k] = null;else
      this.exits[k] = (v + this.rot) % 6;
      this.arcType[k] = ["s", null, "b", "s", null, "b"][
      (k - this.rot + 6) % 6];
      // small or big
    } // for k
  } // Hexagon.constructor

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  size() {
    // coordinates of centre
    this.xc = orgx + this.kx * 1.5 * radius;
    this.yc = orgy + this.ky * radius * rac3;
    if (this.kx & 1) this.yc -= radius * rac3s2; // odd columns, centre is a bit higher

    this.vertices = new Array(6).fill(0).map((v, k) => ({
      x: this.xc + radius * mcos((k - 2) * mPI / 3),
      y: this.yc + radius * msin((k - 2) * mPI / 3) }));

    this.vertices[6] = this.vertices[0]; // makes things easier by avoiding many "% 6" in calculating other calculations

    this.middle = new Array(6).
    fill(0).
    map((p, k) => lerp(this.vertices[k], this.vertices[k + 1], 0.5));

    this.extCenters = new Array(6).fill(0).map((v, k) => ({
      x: this.xc + rac3 * radius * mcos((k - 1) * mPI / 3 - mPIS2),
      y: this.yc + rac3 * radius * msin((k - 1) * mPI / 3 - mPIS2) }));


    // initial angle
    this.a0 = this.rot * mPI / 3; // angle shift due to cell orientation
  } // Hexagon.prototype.size

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  drawHexagon() {
    ctx.beginPath();
    this.vertices.forEach((p, k) => {
      if (k == 0) ctx.moveTo(p.x, p.y);else
      ctx.lineTo(p.x, p.y);
    });
    ctx.lineWidth = 0.5;
    ctx.strokeStyle = "#fff";
    ctx.stroke();
  } // Hexagon.prototype.drawHexagon

  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  getNeighbor(edge) {
    const kx = this.kx + [0, 1, 1, 0, -1, -1][edge];
    const ky =
    this.ky +
    [
    [-1, 0, 1, 1, 1, 0],
    [-1, -1, 0, 1, 0, -1]][
    this.kx & 1][edge];
    if (kx < 0 || kx >= nbx || ky < 0 || ky >= nby) return false;
    return grid[ky][kx];
  }
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  setNeighbors() {
    this.neighbors = [];
    for (let k = 0; k < 6; ++k) {
      this.neighbors[k] = this.getNeighbor(k);
    } // for k
  } // setNeighbors
} //class Hexagon

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

function createGrid() {
  let line;

  perx = intAlea(1, 6);
  pery = intAlea(1, 5);
  perx = nbx;
  pery = nby;

  pergrid = [];
  for (let ky = 0; ky < pery; ++ky) {
    pergrid[ky] = line = []; // new line
    for (let kx = 0; kx < perx; ++kx) {
      line[kx] = intAlea(3);
    } // for let kx
  } // for ky

  grid = [];
  for (let ky = 0; ky < nby; ++ky) {
    grid[ky] = line = []; // new line
    for (let kx = 0; kx < nbx; ++kx) {
      line[kx] = new Hexagon(kx, ky);
      line[kx].size();
    } // for let kx
  } // for ky
} // createGrid
//-----------------------------------------------------------------------------
function makeLines() {
  /* creates description of line - not actuals lines with actual coordinates, just the relation between consecutive arcs in successive cells.
   */
  grid.forEach(line => line.forEach(cell => cell.lines = new Array(6)));

  let lines = [];
  // open lines first
  grid.forEach((row) =>
  row.forEach(cell => {
    for (let edge = 0; edge < 6; ++edge) {
      if (cell.exits[edge] === null) continue; // no line to begin here
      let neigh = cell.neighbors[edge];
      if (!neigh || neigh.exits[(edge + 3) % 6] === null) {
        // check this is actually the start of a line
        lines.push(makeLine(cell, edge));
      } // if true start
    } // for edge
  }));

  // remaining lines (closed)
  // open lines first
  grid.forEach((row) =>
  row.forEach(cell => {
    for (let edge = 0; edge < 6; ++edge) {
      if (cell.exits[edge] === null) continue; // no line to begin here
      if (cell.lines[edge]) continue; // already a line here;
      lines.push(makeLine(cell, edge));
    } // for edge
  }));


  return lines;
} // makeLines

//-----------------------------------------------------------------------------
function makeLine(cell, edge) {
  /* creates a line entering this cell by this edge. */

  const line = { hue: intAlea(360), sat: intAlea(50, 100), segments: [] };
  let rcell = cell,
  redge = edge; // running cell and edge
  let segment;
  do {
    segment = { cell: rcell, kentry: redge };
    rcell.lines[redge] = line;
    line.segments.push(segment);
    let opp = rcell.exits[redge];
    segment.kexit = opp;
    let ncell = rcell.neighbors[opp];
    if (ncell === false) break; // end of line - no cell beyond exit edge
    if (ncell.exits[(opp + 3) % 6] === null) break; // end of line - no line starting here in next cell
    rcell = ncell;
    redge = (opp + 3) % 6;
    if (rcell == cell && redge == edge) {
      // back to start
      line.closed = true; // end of closed line
      break;
    }
  } while (true);
  return line;
} // makeLine

//-----------------------------------------------------------------------------
function getLinePath(line, k) {
  const path = new Path2D();
  line.segments.forEach(segment => {
    segment.arc.addPath(path, k);
  });
  if (line.closed) path.closePath();
  return path;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class Arc {
  constructor(c, radius, a0, a1, ccw) {
    if (!c) return this; // empty arcs allowed
    this.c = c;
    this.radius = radius; // this is the radius at the middle width of the arc - actual radius depends on k ("track" index)
    this.a0 = a0; // 1st angle
    this.a1 = a1;
    this.ccw = ccw;
    // evaluate length
    if (ccw) [a0, a1] = [a1, a0];
    let len = (a1 - a0) % m2PI;
    if (len < 0) len += m2PI;
    this.deltaAng = ccw ? -len : len;
    this.len = len * radius;
  }
  addPath(path) {
    /* paths are simply juxtaposed rather than connected into a single path, because Chrome (not FF)
        generates weird artifacts when connecting 2 arcs. Even though the line connecting the ends of two arcs are at
        a distance < 1e-10, the lineJoin is quite visible and may have any direction
        would probably be invisible with thin lines but ugly with thick ones
        */
    if (!this.radius) {
      this.addPathLin(path, this.pos0, this.pos1);
      return;
    }

    let np = new Path2D();
    np.arc(this.c.x, this.c.y, this.radius, this.a0, this.a1, this.ccw);
    path.addPath(np);
  }

  addPathPartial(path, headPos, tailPos) {
    if (!this.radius) {
      this.addPathLin(path, tailPos, headPos);
      return;
    }

    let a0 = this.a0,
    a1 = this.a1;

    let da0 = 0,
    da1 = 1;
    if (headPos < this.pos1) {
      da1 = (headPos - this.pos0) / (this.pos1 - this.pos0);
    }
    if (tailPos >= this.pos0) {
      da0 = (tailPos - this.pos0) / (this.pos1 - this.pos0);
    }
    if (da0 > da1) da0 = da1 - 0.001;
    a0 = da0 * this.deltaAng + this.a0;
    a1 = da1 * this.deltaAng + this.a0;
    const np = new Path2D();
    np.arc(this.c.x, this.c.y, this.radius, a0, a1, this.ccw);
    path.addPath(np);
  }
  addPathLin(path, begin, eend) {
    const a0 = mmin(1, mmax(0, (begin - this.pos0) / this.len));
    const a1 = mmin(1, mmax((eend - this.pos0) / this.len));
    const p0 = lerp(this.p0, this.p1, a0);
    const p1 = lerp(this.p0, this.p1, a1);
    const np = new Path2D();
    np.moveTo(p0.x, p0.y);
    np.lineTo(p1.x, p1.y);
    path.addPath(np);
  }

  reverse() {
    [this.a0, this.a1] = [this.a1, this.a0];
    this.ccw = !this.ccw;
    this.deltaAng = -this.deltaAng;
  }}
// class Arc
//-----------------------------------------------------------------------------
function reverseLine(line) {
  line.reverse = !line.reverse;
  line.segments.reverse().forEach(segment => {
    [segment.kentry, segment.kexit] = [segment.kexit, segment.kentry];
    segment.arc.reverse();
  });
}

//-----------------------------------------------------------------------------
class MotionPath {
  constructor(line, k) {
    let newArc, newRadius, newAngle, arcLength, newCenter, newa0, newCcw;
    line.motionPath = line.motionPath || [];
    line.motionPath[k] = this;

    this.line = line;
    this.kLine = k;
    this.movingLength = alea(2, 6) * radius;

    /* creates set of arcs starting out of the display and ending at the beginning of the line
        of course, arc is created moving backwards, starting from line and ending out of the screen
        this set of arcs can be emtpty, for lines which already have parts out of the display area
        */
    // make copy of arcs of line, with appropriate radii(depending on k)
    this.arcs = line.segments.map(segment => {
      let arc = new Arc();
      Object.assign(arc, segment.arc);
      // update radius and length
      const dr = ((nbLines - 1) / 2 - k) * trackWidth;
      arc.radius = segment.arc.radius + (segment.arc.ccw ? -dr : .........完整代码请登录后点击上方下载按钮下载查看

网友评论0