| <!DOCtype html>
|
| <html>
|
| <style>
|
| * {
|
| box-sizing: border-box;
|
| }
|
|
|
| html,
|
| body {
|
| height: 100%;
|
| width: 100%;
|
| }
|
|
|
| body {
|
| margin: 0;
|
| overflow: hidden;
|
| }
|
| </style>
|
| <body>
|
| <script>
|
| "use strict";
|
| function getFullScreenParams(ctx) {
|
| const { width, height } = ctx.canvas;
|
| return [0, 0, width, height];
|
| }
|
| function getCtxUtils(ctx) {
|
| function clear() {
|
| const params = getFullScreenParams(ctx);
|
| ctx.clearRect(...params);
|
| }
|
| function save(callback) {
|
| ctx.save();
|
| callback();
|
| ctx.restore();
|
| }
|
| function drawPath(callback) {
|
| ctx.beginPath();
|
| callback();
|
| ctx.closePath();
|
| }
|
| return {
|
| clear,
|
| save,
|
| drawPath
|
| };
|
| }
|
| function initCanvas(resizeCallback, parent = document.body) {
|
| function create() {
|
| const canvas = document.createElement('canvas');
|
| const ctx = canvas.getContext('2d');
|
| return [canvas, ctx];
|
| }
|
| function resize() {
|
| const { clientHeight, clientWidth } = parent;
|
| canvas.height = clientHeight;
|
| canvas.width = clientWidth;
|
| resizeCallback();
|
| }
|
| function append() {
|
| parent.innerHTML = '';
|
| parent.appendChild(canvas);
|
| }
|
| const [canvas, ctx] = create();
|
| resize();
|
| append();
|
| window.addEventListener('resize', resize);
|
| return ctx;
|
| }
|
| class Curve {
|
| constructor(origin, angle, length = 400) {
|
| this.angle = angle;
|
| this.length = length;
|
| this._angleOffset = 40;
|
| this.percentage = 0;
|
| this.speed = (Math.random() * 0.005) + 0.0005;
|
| this.randColor = () => {
|
| const { floor, random } = Math;
|
| return ['cyan', 'magenta', 'yellow'][floor(random() * 3)];
|
| };
|
| this.getEndPoint = () => {
|
| const { cos, sin } = Math;
|
| const x = cos(this.angle) * this.length;
|
| const y = sin(this.angle) * this.length;
|
| return [x, y];
|
| };
|
| this.getStartControlPoint = () => {
|
| const { cos, sin } = Math;
|
| const angle = this.angle - (this._angleOffset / 60);
|
| const length = this.length / 3;
|
| const x = cos(angle) * length;
|
| const y = sin(angle) * length;
|
| return [x, y];
|
| };
|
| this.getEndControlPoint = () => {
|
| const { cos, sin } = Math;
|
| const angle = this.angle + (this._angleOffset / 60);
|
| const length = (this.length / 3) * 2;
|
| const x = cos(angle) * length;
|
| const y = sin(angle) * length;
|
| return [x, y];
|
| };
|
| this.animate = () => {
|
| function getXY() {
|
| const x = cubicN(percentage, 0, startControlPoint[0], endControlPoint[0], end[0]);
|
| const y = cubicN(percentage, 0, startControlPoint[1], endControlPoint[1], end[1]);
|
| return [x, y];
|
| }
|
| // cubic helper formula
|
| function cubicN(T, a, b, c, d) {
|
| const t2 = T * T;
|
| const t3 = t2 * T;
|
| return a + (-a * 3 + T * (3 * a - a * T)) * T + (3 * b + T * (-6 * b + b * 3 * T)) * T + (c * 3 - c * 3 * T) * t2 + d * t3;
|
| }
|
| const { startControlPoint, endControlPoint, end, percentage } = this;
|
| if (!this.isComplete) {
|
| this.percentage += this.speed;
|
| this.currentPoint = getXY();
|
| }
|
| };
|
| this.start = origin;
|
| this.currentPoint = [0, 0];
|
| this.end = this.getEndPoint();
|
| this.startControlPoint = this.getStartControlPoint();
|
| this.endControlPoint = this.getEndControlPoint();
|
| this.color = this.randColor();
|
| }
|
| get isComplete() {
|
| return this.percentage >= 1;
|
| }
|
| }
|
| class Burst {
|
| constructor(x, y) {
|
| this.curves = [];
|
| this.render = (ctx) => {
|
| function renderCurve(curve) {
|
| const { currentPoint, endControlPoint, startControlPoint, end } = curve;
|
| save(() => {
|
| drawPath(() => {
|
| ctx.moveTo(0, 0);
|
| ctx.strokeStyle = 'rgba(80,80,80,0.4)';
|
| ctx.bezierCurveTo(...startControlPoint, ...endControlPoint, ...end);
|
| ctx.stroke();
|
| });
|
| });
|
| save(() => {
|
| ctx.translate(x, y);
|
| drawPath(() => {
|
| let [xi, yi] = currentPoint;
|
| xi -= x;
|
| yi -= y;
|
| ctx.fillStyle = curve.color;
|
| ctx.moveTo(0, 0);
|
| ctx.arc(xi, yi, 4, 0, Math.PI * 2);
|
| ctx.fill();
|
| });
|
| });
|
| }
|
| function renderCurves() {
|
| save(() => {
|
| ctx.translate(x, y);
|
| curves.forEach(renderCurve);
|
| });
|
| }
|
| function renderBackground() {
|
| save(() => {
|
| ctx.translate(0, 0);
|
| ctx.fillStyle = '#101219';
|
| drawPath(() => ctx.fillRect(...getFullScreenParams(ctx)));
|
| });
|
| }
|
| const { clear, save, drawPath } = getCtxUtils(ctx);
|
| const { curves, x, y } = this;
|
| clear();
|
| renderBackground();
|
| renderCurves();
|
| };
|
| this._x = x;
|
| this._y = y;
|
| this.create();
|
| }
|
| get x() { return this._x; }
|
| set x(value) {
|
| this._x = value;
|
| if (this.onPositionChange)
|
| this.onPositionChange();
|
| }
|
| get y() { return this._y; }
|
| set y(value) {
|
| this._y = value;
|
| if (this.onPositionChange)
|
| this.onPositionChange();
|
| }
|
| create() {
|
| const chunkSize = 6 / 32;
|
| for (let i = 0; i <= 6; i += chunkSize) {
|
| this.curves.push(new Curve([this.x, this.y], i, 1300));
|
| }
|
| }
|
| }
|
| window.onload = init;
|
| function init() {
|
| function animate() {
|
| burst.curves.forEach((curve, i) => {
|
| if (curve.isComplete) {
|
| const chunkSize = 6 / 32;
|
| const newCurve = new Curve([burst.x, burst.y], i * chunkSize, 1300);
|
| burst.curves.splice(i, 1, newCurve);
|
| }
|
| else {
|
| curve.animate();
|
| }
|
| });
|
| burst.render(ctx);
|
| requestAnimationFrame(animate);
|
| }
|
| let [cx, cy] = [window.innerWidth / 2, window.innerHeight / 2];
|
| const burst = new Burst(cx, cy);
|
| const ctx = initCanvas(() => {
|
| [cx, cy] = [window.innerWidth / 2, window.innerHeight / 2];
|
| burst.x = cx;
|
| burst.y = cy;
|
| });
|
| burst.render(ctx);
|
| requestAnimationFrame(animate);
|
| }
|
|
|
| </script>
|
| </body>
|
| </html> |
0 Comments
Please don't spam comments Thank You.