这是一款html5 canvas绘制模拟声波扩散线性传播动画特效。canvas元素使用JavaScript在网页上绘制图像。画布是一个矩形区域,您可以控制其每一像素。canvas拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。
使用方法
在页面引入以下JavaScript代码
<script> var ClassicalNoise = function(r) { // Classic Perlin noise in 3D, for comparison 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]; } }; ClassicalNoise.prototype.dot = function(g, x, y, z) { return g[0]*x + g[1]*y + g[2]*z; }; ClassicalNoise.prototype.mix = function(a, b, t) { return (1.0-t)*a + t*b; }; ClassicalNoise.prototype.fade = function(t) { return t*t*t*(t*(t*6.0-15.0)+10.0); }; // Classic Perlin noise, 3D version ClassicalNoise.prototype.noise = function(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.perm[X+1+this.perm[Y+1+this.perm[Z]]] % 12; var gi111 = this.perm[X+1+this.perm[Y+1+this.perm[Z+1]]] % 12; // The gradients of each corner are now: // g000 = grad3[gi000]; // g001 = grad3[gi001]; // g010 = grad3[gi010]; // g011 = grad3[gi011]; // g100 = grad3[gi100]; // g101 = grad3[gi101]; // g110 = grad3[gi110]; // g111 = grad3[gi111]; // Calculate noise contributions from each of the eight corners var n000= this.dot(this.grad3[gi000], x, y, z); var n100= this.dot(this.grad3[gi100], x-1, y, z); var n010= this.dot(this.grad3[gi010], x, y-1, z); var n110= this.dot(this.grad3[gi110], x-1, y-1, z); var n001= this.dot(this.grad3[gi001], x, y, z-1); var n101= this.dot(this.grad3[gi101], x-1, y, z-1); var n011= this.dot(this.grad3[gi011], x, y-1, z-1); var n111= this.dot(this.grad3[gi111], x-1, y-1, z-1); // Compute the fade curve value for each of x, y, z var u = this.fade(x); var v = this.fade(y); var w = this.fade(z); // Interpolate along x the contributions from each of the corners var nx00 = this.mix(n000, n100, u); var nx01 = this.mix(n001, n101, u); var nx10 = this.mix(n010, n110, u); var nx11 = this.mix(n011, n111, u); // Interpolate the four results along y var nxy0 = this.mix(nx00, nx10, v); var nxy1 = this.mix(nx01, nx11, v); // Interpolate the two last results along z var nxyz = this.mix(nxy0, nxy1, w); return nxyz; }; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _class2, _temp, _initialiseProps; function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } //*??????????????????????????????/ // Utils //*?????????????????????????????*/ function cycle(value, total) { return (value % total + total) % total; } function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) { return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed; } function movePointAtAngle(point, angle, distance) { return { x: point.x + Math.cos(angle) * distance, y: point.y + Math.sin(angle) * distance }; } //*??????????????????????????????/ // Element //*?????????????????????????????*/ var Element = function Element() { var _this = this; _classCallCheck(this, Element); this.dpr = 1; this.toValue = function (value) { return value * _this.dpr; }; this.draw = function () {}; this.update = function () {}; }; //*??????????????????????????????/ // Wave //*?????????????????????????????*/ var Wave = (_temp = _class2 = function (_Element) { _inherits(Wave, _Element); function Wave(pointAmount, p1, p2) { _classCallCheck(this, Wave); var _this2 = _possibleConstructorReturn(this, (Wave.__proto__ || Object.getPrototypeOf(Wave)).call(this)); _initialiseProps.call(_this2); _this2.p1 = p1; _this2.p2 = p2; var dx = p2.x - p1.x; var dy = p2.y - p1.y; var vx = dx / (pointAmount - 1); var vy = dy / (pointAmount - 1); _this2.vertices = new Array(pointAmount).fill(null).map(function (p, i) { return { x: p1.x + vx * i, y: p1.y + vy * i }; }); return _this2; } return Wave; }(Element), _initialiseProps = function _initialiseProps() { var _this3 = this; this.draw = function (_ref) { var ctx = _ref.ctx; ctx.lineCap = 'round'; ctx.lineWidth = _this3.toValue(2); ctx.strokeStyle = '#ccc'; ctx.beginPath(); ctx.moveTo(_this3.vertices[0].x, _this3.vertices[0].y); for (var k = 0; k < _this3.vertices.length - 1; k++) { var p1 = _this3.vertices[k]; var p2 = _this3.vertices[k + 1]; var cpx = (p1.x + p2.x) / 2; var cpy = (p1.y + p2.y) / 2; // ctx.fillStyle = 'white'; // ctx.fillRect(p1.x, p1.y, 10, 10); if (k === _this3.vertices.length - 2) { ctx.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y); } else { ctx.quadraticCurveTo(p1.x, p1.y, cpx, cpy); } } ctx.stroke(); }; this.update = function (_ref2) { var tick = _ref2.tick; var l = _this3.vertices.length; var r = 1 / l; _this3.vertices = _this3.vertices.map(function (p, i) { return { x: p.x, y: p.y + _this3.toValue(Math.sin(tick / 10 + i)) }; }); }; }, _temp); //*??????????????????????????????/ // Gate //*?????????????????????????????*/ var Gate = function (_Element2) { _inherits(Gate, _Element2); function Gate(_ref3) { var radius = _ref3.radius, amount = _ref3.amount; _classCallCheck(this, Gate); var _this4 = _possibleConstructorReturn(this, (Gate.__proto__ || Object.getPrototypeOf(Gate)).call(this)); _this4.reset = function () { _this4.setValues(); _this4.setupWaves(); }; _this4.draw = function (canvas) { _this4.drawBg(canvas); canvas.ctx.save(); canvas.ctx.beginPath(); canvas.ctx.arc(_this4.hw, _this4.hh, _this4.radius, 0, 2 * Math.PI); canvas.ctx.clip(); _this4.waves.map(function (wave) { wave.draw(canvas); wave.update(canvas); }); canvas.ctx.restore(); }; _this4.amount = amount; _this4.radius = _this4.toValue(radius); _this4.diameter = _this4.radius * 2; _this4.setValues(); _this4.setupWaves(); return _this4; } _createClass(Gate, [{ key: 'setValues', value: function setValues() { this.w = this.toValue(window.innerWidth); this.h = this.toValue(window.innerHeight); this.hw = this.w / 2; this.hh = this.h / 2; } }, { key: 'setupWaves', value: function setupWaves() { // Gate dims this.px = this.hw - this.radius; this.py = this.hh - this.radius; this.pw = this.diameter; this.ph = this.diameter; this.waves = []; var extend = 2; for (var i = -extend; i < this.amount + extend; i++) { var dy = this.ph / this.amount * i; var p1 = { x: this.px, y: this.py + dy }; var p2 = { x: this.px + this.pw, y: this.py + dy }; this.waves.push(new Wave(10, p1, p2)); } } }, { key: 'drawBg', value: function drawBg(canvas) { canvas.ctx.beginPath(); var gradient = canvas.ctx.createLinearGradient(this.px, this.py, this.px, this.py + this.diameter); gradient.addColorStop(0, '#2a5298'); gradient.addColorStop(1, '#4298b7'); var wobble = this.toValue(Math.sin(canvas.tick / 20) * 10); var offset = this.toValue(5); canvas.ctx.arc(this.hw, this.hh, this.radius + offset + wobble, 0, 2 * Math.PI); canvas.ctx.fillStyle = gradient; canvas.ctx.fill(); } }]); return Gate; }(Element); //*??????????????????????????????/ // Ring //*?????????????????????????????*/ var Ring = function (_Element3) { _inherits(Ring, _Element3); function Ring(_ref4) { var radius = _ref4.radius, pointAmount = _ref4.pointAmount, speed = _ref4.speed, decay = _ref4.decay, acceleration = _ref4.acceleration, wobble = _ref4.wobble, warp = _ref4.warp; _classCallCheck(this, Ring); var _this5 = _possibleConstructorReturn(this, (Ring.__proto__ || Object.getPrototypeOf(Ring)).call(this)); _this5.draw = function (_ref5) { var ctx = _ref5.ctx; var p = _this5.points, v = _this5.toValue; ctx.lineWidth = _this5.lineWidth; ctx.strokeStyle = _this5.stroke; ctx.beginPath(); ctx.moveTo((p[cycle(-1, p.length)].x + p[0].x) / 2, (p[cycle(-1, p.length)].y + p[0].y) / 2); for (var i = 0; i < p.length; i++) { ctx.quadraticCurveTo(p[i].x, p[i].y, (p[i].x + p[cycle(i + 1, p.length)].x) / 2, (p[i].y + p[cycle(i + 1, p.length)].y) / 2); } ctx.closePath(); ctx.globalCompositeOperation = 'lighter'; ctx.stroke(); ctx.globalCompositeOperation = 'source-over'; }; _this5.update = function (_ref6) { var tick = _ref6.tick; _this5.speed *= _this5.acceleration; // this.lineWidth *= (this.acceleration / 2); _this5.points = _this5.points.map(function (p, i) { var wobbleAmount = Math.sin(tick / 20 + i * _this5.radOff) * _this5.wobble; var warpAmount = Math.cos(tick / 100) * _this5.warp; var _movePointAtAngle = movePointAtAngle(p, p.radian, _this5.speed), x = _movePointAtAngle.x, y = _movePointAtAngle.y; return _extends({}, p, { x: x + wobbleAmount - warpAmount, y: y - wobbleAmount + warpAmount }); }); if (!_this5.dead) { --_this5.decay; if (_this5.decay === 0) { _this5.dead = true; } } }; _this5.points = []; _this5.w = _this5.toValue(window.innerWidth); _this5.h = _this5.toValue(window.innerHeight); _this5.hw = _this5.w / 2; _this5.hh = _this5.h / 2; _this5.speed = _this5.toValue(speed); _this5.decay = decay; _this5.acceleration = acceleration; _this5.lineWidth = _this5.toValue(1); _this5.warp = _this5.toValue(warp); _this5.wobble = _this5.toValue(wobble); _this5.radOff = 2 * Math.PI / pointAmount; _this5.opacityStroke = 1; _this5.opacityFill = 0; _this5.opacityDecay = 1 / _this5.decay; _this5.center = { x: _this5.hw, y: _this5.hh }; for (var i = 0; i < pointAmount; i++) { var radian = Math.PI * 2 / pointAmount * i; var x = _this5.center.x + radius * Math.cos(radian); var y = _this5.center.y + radius * Math.sin(radian); _this5.points.push({ x: x, y: y, radian: radian }); } return _this5; } _createClass(Ring, [{ key: 'stroke', get: function get() { this.opacityStroke -= this.opacityDecay; var r = Math.floor(170 + Math.sin(this.decay / 10) * 60); var g = Math.floor(130 + Math.sin(this.decay / 5) * 40); // const b = Math.floor(130 + (Math.sin(this.decay / 5) * 40)); return 'rgba(' + r + ', ' + g + ', 200, ' + this.opacityStroke + ')'; } }]); return Ring; }(Element); //*??????????????????????????????/ // Portal //*?????????????????????????????*/ var Portal = function () { function Portal(time, ringConfig) { var _this6 = this; _classCallCheck(this, Portal); this.reset = function () { _this6.rings = []; }; this.draw = function () {}; this.update = function (_ref7) { var ctx = _ref7.ctx, tick = _ref7.tick; if (tick % _this6.time === 0) { _this6.addRing(); } // filter dead while drawing and updating _this6.rings = _this6.rings.filter(function (ring) { ring.draw({ ctx: ctx, tick: tick }); ring.update({ ctx: ctx, tick: tick }); return ring.dead !== true; }); }; this.time = time; this.ringConfig = ringConfig; this.rings = [new Ring(this.ringConfig)]; } _createClass(Portal, [{ key: 'addRing', value: function addRing() { this.rings.push(new Ring(this.ringConfig)); } }]); return Portal; }(); //*??????????????????????????????/ // Background //*?????????????????????????????*/ var Background = function (_Element4) { _inherits(Background, _Element4); function Background() { var _ref8; var _temp2, _this7, _ret; _classCallCheck(this, Background); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp2 = (_this7 = _possibleConstructorReturn(this, (_ref8 = Background.__proto__ || Object.getPrototypeOf(Background)).call.apply(_ref8, [this].concat(args))), _this7), _this7.draw = function (_ref9) { var ctx = _ref9.ctx, canvas = _ref9.canvas; var w = canvas.width; var h = canvas.height; // const gradient1 = ctx.createLinearGradient(0, 0, 0, h); // gradient1.addColorStop(0, '#aa5eed'); // gradient1.addColorStop(1, '#b7f7ed'); // ctx.fillStyle = gradient1; // ctx.fillRect(0, 0, w, h); var gradient2 = ctx.createRadialGradient(w / 2, h / 2, 0, w / 2, h / 2, w); gradient2.addColorStop(1, '#20008c'); gradient2.addColorStop(0, '#86efcc'); ctx.fillStyle = gradient2; ctx.fillRect(0, 0, w, h); // ctx.globalCompositeOperation = 'source-over'; }, _temp2), _possibleConstructorReturn(_this7, _ret); } return Background; }(Element); //*??????????????????????????????/ // Canvas //*?????????????????????????????*/ var Canvas = function () { function Canvas() { var _this8 = this; var elements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; _classCallCheck(this, Canvas); this.setPointer = function (_ref10) { var clientX = _ref10.clientX, clientY = _ref10.clientY; var x = clientX; var y = clientY; _this8.mouse = { x: x, y: y }; }; this.setCanvasSize = function () { _this8.canvas.width = window.innerWidth * _this8.dpr; _this8.canvas.height = window.innerHeight * _this8.dpr; _this8.canvas.style.width = window.innerWidth + 'px'; _this8.canvas.style.height = window.innerHeight + 'px'; }; this.render = function () { _this8.draw(); _this8.update(); ++_this8.tick; window.requestAnimationFrame(_this8.render); }; // setup a canvas this.canvas = document.getElementById('canvas'); this.dpr = 1; this.ctx = this.canvas.getContext('2d'); this.ctx.scale(this.dpr, this.dpr); // stores this.elements = elements; this.tick = 0; this.mouse = { x: 0, y: 0 }; // run this.setCanvasSize(); this.setupListeners(); } _createClass(Canvas, [{ key: 'setupListeners', value: function setupListeners() { window.addEventListener('resize', this.setCanvasSize); window.addEventListener('mousemove', this.setPointer); } }, { key: 'addElement', value: function addElement(newElement) { this.elements = [].concat(_toConsumableArray(this.elements), [newElement]); return this.elements.length - 1; } }, { key: 'removeElement', value: function removeElement(deleteIndex) { this.elements = this.elements.filter(function (el, i) { return i !== deleteIndex; }); return this.elements; } }, { key: 'update', value: function update() { var _this9 = this; this.elements.map(function (_ref11) { var update = _ref11.update; return update(_this9); }); } }, { key: 'draw', value: function draw() { var _this10 = this; this.elements.map(function (_ref12) { var draw = _ref12.draw; return draw(_this10); }); } }]); return Canvas; }(); var canvas = new Canvas([new Background(), new Portal(5, { radius: 0, pointAmount: 3, speed: 2, decay: 200, acceleration: 1.02, wobble: 1, warp: 3 })]); canvas.render();</script>