canvas妖风线条动画效果代码
代码语言:html
所属分类:动画
代码描述: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