UNPKG

wavy-canvas

Version:

Create a wave animation on the canvas

187 lines (149 loc) 5 kB
import 'hidpi-canvas/dist/hidpi-canvas'; import {default as raf, cancel as caf} from 'raf'; import objectAssign from 'object-assign'; import easing from 'easing-js'; import Circle from './Circle'; import offsetParent from './offsetParent'; export default function make(options) { var rafId; var canvas; var context; var canvasWidth; var canvasHeight; var circles = []; var totalIterations; var currentIteration; var defaultCenterWaveSelector = '.js-wavy-center'; class Wavy { constructor(options) { this.options = objectAssign({}, options); this.hexFillColor = options.hexFillColor; this.hexStrokeColor = options.hexStrokeColor; this.onlyWaves = typeof options.onlyWaves === 'undefined' ? true : options.onlyWaves; this.radiuses = options.radiuses; this.duration = options.duration; this.selector = options.selector; this.centerWave = options.centerWave; this.centerWaveSelector = options.centerWaveSelector || defaultCenterWaveSelector; this.selectorEl = this.options.selectorEl || null; this.generateCircles(); this.createCanvas(); this.setupCanvas(); this.setupCenterWave(); this.setupAnimation(); this.attachEvents(); } generateCircles() { for (let radius of this.radiuses) { circles.push(new Circle(radius, this.hexFillColor, this.hexStrokeColor)); } } setupCanvas() { canvas.removeAttribute('width'); canvas.removeAttribute('height'); canvas.removeAttribute('style'); canvasWidth = canvas.clientWidth; canvasHeight = canvas.clientHeight; canvas.width = canvasWidth; canvas.height = canvasHeight; // hidpi-canvas overrides getContext() method and set again width and height for canvas // based on screen hidpi ratio, then canvas is rescaled down by CSS width and height props context = canvas.getContext('2d'); } setupCenterWave() { if (this::hasOptionsCenterWave()) { return; } const centerWaveEl = this.selectorEl.querySelector(this.centerWaveSelector); if (centerWaveEl === null) { this.centerWave = { x: canvasWidth / 2, y: canvasHeight / 2 }; return; } // We have centerWave element somewhere in the DOM // and centerWave wasn't set directly thus we have to calculate centerWave manually this.centerWave = offsetParent(this.selectorEl, centerWaveEl); } start() { if (!isDrawing()) { this::draw(); } return this; } stop() { if (isDrawing()) { caf(rafId); rafId = undefined; } return this; } attachEvents() { window.addEventListener('resize', () => { this.setupCanvas(); this.setupCenterWave(); this.setupAnimation(); }); } setupAnimation() { totalIterations = this.duration / 1000 * 60; currentIteration = 0; for (let circle of circles) { circle.x = this.centerWave.x; circle.y = this.centerWave.y; circle.setFillAlpha(0); } } createCanvas() { canvas = document.createElement('canvas'); const el = this.selectorEl ? this.selectorEl : document.querySelector(this.selector); if (el === null) { throw new Error(`Not found element: ${this.selector} in the DOM`); } el.appendChild(canvas); this.selectorEl = el; } } /** * @private * @returns {boolean} */ function hasOptionsCenterWave() { return ( this.options.centerWave !== null && typeof this.options.centerWave === 'object' && typeof this.options.centerWave.x === 'number' && typeof this.options.centerWave.y === 'number' ); } /** * @private */ function draw() { context.clearRect(0, 0, canvasWidth, canvasHeight); for (let circle of circles) { // This is madness... // When changeInValue is equal to -1 which is what we basically want... // Safari has weird bug and display black, non-transparent circles in one frame // which looks like animation glitch. // It occurs only in Safari 9.1.1 on OS X 10.11.5 // All easing functions has this form in terms of parameters which they take // (currentIteration, startValue, changeInValue, totalIterations) var opacity = easing.easeInOutCubic(currentIteration, 1, -0.99, totalIterations); var scale = easing.easeInOutCubic(currentIteration, 1, 0.618, totalIterations); circle.setScale({x: scale, y: scale}); if (!this.onlyWaves) { circle.setFillAlpha(opacity); } circle.setStrokeAlpha(opacity); circle.draw(context); } currentIteration = (currentIteration < totalIterations) ? currentIteration + 1 : 0; rafId = raf(() => this::draw()); } function isDrawing() { return typeof rafId !== 'undefined'; } return new Wavy(options); }