scrawl-canvas
Version:
Version 8.9.4 - 19 Nov 2022
311 lines (229 loc) • 10.3 kB
JavaScript
// # RenderAnimation factory
// RenderAnimation objects are animations that aim to simplify coding up Display cycles. They remove the need to worry about Promises, while at the same time exposing a suite of Display cycle hooks - attributes which can accept a function to be run at various points during a 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.
// + `error` - triggers when the Display cycle throws an error.
//
// Note that all the above functions should be normal, synchronous functions. The Animation object will run them within its main, asynchronous (Promise-based) function.
//
// 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 two additional flags, which influence the make functionality:
// + __delay__ - when set to true, will prevent the animation running immediately; to start the animation, invoke its __run__ function
// + __observe__ - 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
// #### Demos:
// + Most Demos use RenderAnimation for managing their Display cycle
// #### Imports
import { animation, artefact, constructors } from '../core/library.js';
import { clear, compile, show } from '../core/document.js';
import { makeAnimationObserver } from '../core/events.js';
import { mergeOver, pushUnique, removeItem, xt, λnull, λthis, Ωempty, isa_boolean } from '../core/utilities.js';
import { animate, resortAnimations } from '../core/animationloop.js';
import { forceUpdate } from '../core/userInteraction.js';
import baseMix from '../mixin/base.js';
// #### 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 (Array.isArray(items.target)) {
let multiReturn = []
items.target.forEach(tempTarget => {
let tempItems = Object.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;
// Without a target, we can progress no further
// if (!target || !target.clear || !target.compile || !target.show) return false;
// All should be good - proceed with animation creation
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;
// 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.error = items.error || λnull;
this.readyToInitialize = true;
// ##### The Display cycle Promise chain
this.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;
}
}
}
// Register in Scrawl-canvas library
this.register();
// The `observer` attribute
const obs = items.observer || false;
if (obs) {
if (isa_boolean(obs)) this.observer = makeAnimationObserver(this, this.target);
else this.observer = makeAnimationObserver(this, this.target, obs);
}
// Start the animation immediately, unless flagged otherwise
if(!items.delay) this.run();
return this;
};
// #### RenderAnimation prototype
let P = RenderAnimation.prototype = Object.create(Object.prototype);
P.type = 'RenderAnimation';
P.lib = 'animation';
P.isArtefact = false;
P.isAsset = false;
// #### Mixins
P = baseMix(P);
// #### RenderAnimation attributes
// + Attributes defined in the [base mixin](../mixin/base.html): __name__.
let 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,
// __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__, __error__
//
// 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.
// + `error` - triggers when the Display cycle throws an error.
commence: null,
afterClear: null,
afterCompile: null,
afterShow: null,
afterCreated: null,
error: 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 obvjects
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 = λthis;
// #### Kill management
// Stops the animation if it is already running, and removes it from the Scrawl-canvas library
P.kill = function () {
this.onKill();
removeItem(animate, this.name);
resortAnimations();
this.deregister();
return true;
};
// #### Get, Set, deltaSet
// No additional get/set functionality required
// #### Prototype functions
// `run` - start the animation, if it is not already running
P.run = function () {
this.onRun();
pushUnique(animate, this.name);
resortAnimations();
if (this.target) this.target.checkAccessibilityValues();
setTimeout(() => forceUpdate(), 20);
return this;
};
// `isRunning` - returns Boolean true if animation is running; false otherwise
P.isRunning = function () {
return (animate.indexOf(this.name) >= 0) ? true : false;
};
// `halt` - stop the animation, if it is already running
P.halt = function () {
this.onHalt();
removeItem(animate, this.name);
resortAnimations();
return this;
};
// #### 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,
// });
// ```
const makeRender = function (items) {
if (!items) return false;
return new RenderAnimation(items);
};
constructors.RenderAnimation = RenderAnimation;
// #### Exports
export {
makeRender,
};