UNPKG

scrawl-canvas

Version:

Responsive, interactive and more accessible HTML5 canvas elements. Scrawl-canvas is a JavaScript library designed to make using the HTML5 canvas element easier, and more fun

362 lines (263 loc) 12.1 kB
// # RenderAnimation factory // RenderAnimation objects are animations that aim to simplify coding up Display cycles. They remove the worry of dealing with the Scrawl-canvas __Display cycle__ while also exposing a suite of Display cycle hook functions - attributes which can accept a function to be run at various points during each Display cycle: // + `commence` - triggers at the start of the Display cycle, before the `clear` cascade begins. // + `afterClear` - triggers when the `clear` cascade completes, before the `compile` cascade begins. // + `afterCompile` - triggers when the `compile` cascade completes, before the `show` cascade begins. // + `afterShow` - triggers at the end of the Display cycle, after the `show` cascade completes. // + `afterCreated` - triggers once, after the first Display cycle completes. // // The RenderAnimation object also supports the Animation object's __animation hook functions__: // + `onRun` - triggers each time the RenderAnimation object's `run` function is invoked // + `onHalt` - triggers each time the RenderAnimation object's `halt` function is invoked // + `onKill` - triggers each time the RenderAnimation object's `kill` function is invoked // // In addition to RenderAnimation attributes, the factory's argument object can include three additional boolean flags, which influence the make functionality: // + __delay__ - default: `false`. When set to true, will prevent the animation running immediately; to start the animation, invoke its __run__ function // + __observe__ - default: `false`. When set to true, will add an IntersectionObserver to the target's DOM element, which in turn assigns a disconnect function to the `RenderAnimation.observe` attribute. The attribute can also be a Javascript object containing options to be applied to the observer (`root`, `rootMargin`, `threshold`) // + __noTarget__ - default: `false`. the `renderAnimation` factory function expects to receive a Canvas or Stack artefact (or an array of such artefacts) in the `target` attribute of its argument object. When no target attribute is supplied, the RenderAnimation object will operate across all Canvas and Stack elements on the page. If the target is not a Canvas or Stack, then set the `noTarget` attribute to `true`. // #### Imports import { artefact, constructors } from '../core/library.js'; import { clear, compile, show } from '../core/display-cycle.js'; import { makeAnimationObserver } from '../core/events.js'; import { doCreate, isa_boolean, mergeOver, xt, λnull, λcloneError, Ωempty } from '../helper/utilities.js'; import { animateAdd, animateIncludes, animateRemove } from '../core/animation-loop.js'; import { forceUpdate } from '../helper/system-flags.js'; import baseMix from '../mixin/base.js'; // Shared constants import { _assign, _isArray, ANIMATION, T_RENDER_ANIMATION } from '../helper/shared-vars.js'; // Local constants (none defined) // #### RenderAnimation constructor const RenderAnimation = function (items = Ωempty) { let target; this.noTarget = (items.noTarget != null) ? items.noTarget : false; // Handle cases where no target has been defined - the animation will affect all stacks and canvases if (!items.target && !items.noTarget) target = { clear: clear, compile: compile, show: show, checkAccessibilityValues: λnull, }; // Handle cases where we have multiple targets - each needs its own render animation else if (_isArray(items.target)) { const multiReturn = []; items.target.forEach(tempTarget => { const tempItems = _assign({}, items); tempItems.name = `${tempItems.name}_${tempTarget.name}`; tempItems.target = tempTarget; multiReturn.push(new RenderAnimation(tempItems)); }); return multiReturn; } // Default case where we have a single target else target = (items.target && items.target.substring) ? artefact[items.target] : items.target; this.makeName(items.name); // These attributes are the same as for the Animation object this.order = (xt(items.order)) ? items.order : this.defs.order; this.onRun = items.onRun || λnull; this.onHalt = items.onHalt || λnull; this.onKill = items.onKill || λnull; this.maxFrameRate = items.maxFrameRate || 60; this.lastRun = 0; this.chokedAnimation = true; // These attributes are specific to RenderAnimation this.target = target; this.commence = items.commence || λnull; this.afterClear = items.afterClear || λnull; this.afterCompile = items.afterCompile || λnull; this.afterShow = items.afterShow || λnull; this.afterCreated = items.afterCreated || λnull; this.readyToInitialize = true; // Register in Scrawl-canvas library this.register(); // The `observer` attribute const obs = items.observer != null ? items.observer : true; this.observer = null; if (obs) { // Edge case: canvas/stack removed or replaced, affecting DOM. Best to queue the creation of the observer until after the DOM has settled down // + Fix resulted from errors noticed in demo DOM-017 setTimeout(() => { if (isa_boolean(obs)) this.observer = makeAnimationObserver(this, this.target); else this.observer = makeAnimationObserver(this, this.target, obs); }, 0); } // Start the animation immediately, unless flagged otherwise if(!items.delay) this.run(); return this; }; // #### RenderAnimation prototype const P = RenderAnimation.prototype = doCreate(); P.type = T_RENDER_ANIMATION; P.lib = ANIMATION; P.isArtefact = false; P.isAsset = false; // #### Mixins baseMix(P); // #### RenderAnimation attributes const defaultAttributes = { // __order__ - positive integer Number. Determines the order in which each animation object will be actioned during the Display cycle. Higher order animations will be processed after lower order animations. order: 1, // __maxFrameRate__ - positive integer Number. A frames-per-second choke to prevent animation running too fast. maxFrameRate: 60, // __onRun__, __onHalt__, __onKill__ // // The [Animation object](./animation.html) supports the following ___animation hook functions___: // + `onRun` - triggers each time the animation object's `run` function is invoked. // + `onHalt` - triggers each time the animation object's `halt` function is invoked. // + `onKill` - triggers each time the animation object's `kill` function is invoked. onRun: null, onHalt: null, onKill: null, // __commence__, __afterClear__, __afterCompile__, __afterShow__, __afterCreated__ // // RenderAnimation objects support the following ___Display cycle hook functions___: // + `commence` - triggers at the start of the Display cycle, before the `clear` cascade begins. // + `afterClear` - triggers when the `clear` cascade completes, before the `compile` cascade begins. // + `afterCompile` - triggers when the `compile` cascade completes, before the `show` cascade begins. // + `afterShow` - triggers at the end of the Display cycle, after the `show` cascade completes. // + `afterCreated` - triggers once, after the first Display cycle completes. commence: null, afterClear: null, afterCompile: null, afterShow: null, afterCreated: null, // __target__ - handle to the [Stack](./stack.html) or [Cell](./cell.html) wrapper object; each can have its own Display cycle animation. // + Can be supplied in the argument object as either a name-String for the target object, or the target object itself // + Will also accept an Array of Strings and/or objects // + Artefacts that are not Canvases or Stacks can be targets - in such cases the `noTarget` attribute in the factory function's argument object should be set to `true` target: null, }; P.defs = mergeOver(P.defs, defaultAttributes); // #### Packet management // Animations do not take part in the packet or clone systems; they can, however, be used for importing and actioning packets as they retain those base functions P.stringifyFunction = λnull; P.processPacketOut = λnull; P.finalizePacketOut = λnull; P.saveAsPacket = function () { return `[${this.name}, ${this.type}, ${this.lib}, {}]` }; // #### Clone management P.clone = λcloneError; // #### Kill management // Stops the animation if it is already running, and removes it from the Scrawl-canvas library P.kill = function () { this.onKill(); animateRemove(this.name); this.deregister(); return true; }; // #### Get, Set, deltaSet // No additional get/set functionality required // #### Prototype functions // The Display cycle animation function P.fn = function () { if (this.noTarget) { this.commence(); this.afterClear(); this.afterCompile(); this.afterShow(); if (this.readyToInitialize) { this.afterCreated(this); this.readyToInitialize = false; } } else if (this.isRunning()) { this.commence(); this.target.clear(); this.afterClear(); this.target.compile(); this.afterCompile(); this.target.show(); this.afterShow(); if (this.readyToInitialize) { this.target.checkAccessibilityValues(); this.afterCreated(this); this.readyToInitialize = false; } } }; // `run` - start the animation, if it is not already running P.run = function () { this.onRun(); animateAdd(this.name); if (this.target) this.target.checkAccessibilityValues(); setTimeout(() => forceUpdate(), 20); return this; }; // `start` - start the animation, if it is not already running. Will re-run the `afterCreated` hook, which is ignored by the `run()` function. P.start = function () { this.readyToInitialize = true; return this.run(); }; // `isRunning` - returns Boolean true if animation is running; false otherwise P.isRunning = function () { return animateIncludes(this.name); }; // `halt` - stop the animation, if it is already running P.halt = function () { this.onHalt(); animateRemove(this.name); return this; }; // `updateOnce` - if the animation is not running, run it for one Display cycle and halt it again P.updateOnce = function () { if (!this.isRunning()) { this.run(); setTimeout(() => this.halt(), 0); } return this; }; // `updateHook` - a convenience function to update individual hook functions P.updateHook = function (hook = '', func) { switch (hook) { case 'commence' : if (func) this.commence = func; else this.commence = λnull; break; case 'afterClear' : if (func) this.afterClear = func; else this.afterClear = λnull; break; case 'afterCompile' : if (func) this.afterCompile = func; else this.afterCompile = λnull; break; case 'afterShow' : if (func) this.afterShow = func; else this.afterShow = λnull; break; case 'afterCreated' : if (func) this.afterCreated = func; else this.afterCreated = λnull; break; } }; // #### Factory // ``` // let report = function () { // // let testTicker = Date.now(), // testTime, testNow, // testMessage = document.querySelector('#reportmessage'); // // return function () { // // testNow = Date.now(); // testTime = testNow - testTicker; // testTicker = testNow; // // testMessage.textContent = `Screen refresh: ${Math.ceil(testTime)}ms`; // }; // }(); // // scrawl.makeRender({ // // name: 'demo-animation', // target: scrawl.library.canvas['my-canvas-id'], // afterShow: report, // }); // ``` export const makeRender = function (items) { if (!items) return false; return new RenderAnimation(items); }; constructors.RenderAnimation = RenderAnimation;