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
210 lines (144 loc) • 7.9 kB
JavaScript
// # DOM management
// Core DOM element discovery and management functionality
//
// The order in which DOM stack and canvas elements are processed during the display cycle can be changed by setting that element's controller's __order__ attribute to a higher or lower integer value; elements are processed (in batches) from lowest to highest order value
// #### Imports
import { artefact, entitynames, entity } from "./library.js";
import { pushUnique, Ωempty } from "../helper/utilities.js";
import { getPixelRatio, getIgnorePixelRatio } from "./user-interaction.js";
import { releaseArray, requestArray } from '../helper/array-pool.js';
// Shared constants
import { _keys, AUTO, BLOCK, FONT_USERS, NONE, T_CANVAS, ZERO_STR } from '../helper/shared-vars.js';
// Local constants
const ignoredCssProperties = ['bottom', 'boxSizing', 'display', 'height', 'left', 'perspective', 'perspectiveOrigin', 'position', 'right', 'top', 'transform', 'transformOrigin', 'width', 'zIndex'];
// #### DOM element updates
// Scrawl-canvas will batch process all DOM element updates, to minimize disruptive impacts on web page performance. We don't maintain a full/comprehensive 'shadow' or 'virtual' DOM, but Scrawl-canvas does maintain a record of element (absolute) position and dimension data, alongside details of scaling, perspective and any other CSS related data (including CSS classes) which we tell it about, on a per-element basis.
//
// The decision on whether to update a DOM element is mediated through a suite of 'dirty' flags assigned on the Scrawl-canvas artefact object which wraps each DOM element. As part of the compile component of the Scrawl-canvas Display cycle, the code will take a decision on whether the DOM element needs to be updated and insert the artefact's name in the __domShowElements__ array, and set the __domShowRequired__ flag to true, which will then trigger the __domShow()__ function to run at the end of each Display cycle.
//
// The domShow() function is exported, and can be triggered for any DOM-related artefact at any time by invoking it with the artefact's name as the function's argument.
//
// The order in which DOM elements get updated is determined by the __order__ attributes set on the Stack artefact, on Group objects, and (least important) on the element artefact.
// Local variable
const domShowElements = [];
let domShowRequired = false;
// `Exported function` (to modules).
export const setDomShowRequired = (val = true) => domShowRequired = val;
// `Exported function` (to modules).
export const addDomShowElement = function (item = ZERO_STR) {
if (item && item.substring) {
pushUnique(domShowElements, item);
}
};
// `Exported function` (to modules). This is the __main DOM manipulation function__ which will be triggered once during each Display cycle.
export const domShow = function (singleArtefact = ZERO_STR) {
if (domShowRequired || singleArtefact) {
const myartefacts = requestArray();
if (singleArtefact && singleArtefact.substring) myartefacts.push(singleArtefact);
else {
domShowRequired = false;
myartefacts.push(...domShowElements);
domShowElements.length = 0;
}
let i, iz, art, el, style,
p, dims, w, h,
j, jz, items, keys, key, value;
const ignoreDpr = getIgnorePixelRatio();
const dpr = getPixelRatio();
for (i = 0, iz = myartefacts.length; i < iz; i++) {
art = artefact[myartefacts[i]];
if (art) {
el = art.domElement;
if (el) {
// update element classes
if (art.dirtyClasses) {
art.dirtyClasses = false;
if (el.className.substring) el.className = art.classes;
}
style = el.style;
if (style) {
// update non-controlled CSS changes
if (art.dirtyCss) {
art.dirtyCss = false;
items = art.css || Ωempty;
keys = _keys(items);
for (j = 0, jz = keys.length; j < jz; j++) {
key = keys[j];
value = items[key];
// SC no longer cares about browser-specific prefixes
if (!ignoredCssProperties.includes(key)) style[key] = value;
}
}
// update perspective
if (art.dirtyPerspective) {
art.dirtyPerspective = false;
p = art.perspective;
style.perspectiveOrigin = `${p.x} ${p.y}`;
style.perspective = `${p.z}px`;
}
// update position
if (art.dirtyPosition) {
art.dirtyPosition = false;
style.position = art.position;
}
// update dimensions
if (art.dirtyDomDimensions) {
art.dirtyDomDimensions = false;
dims = art.currentDimensions;
w = dims[0];
h = dims[1];
if (art.type === T_CANVAS) {
if (ignoreDpr) {
el.width = w;
el.height = h;
}
else {
if (!art.ignoreCanvasCssDimensions) {
style.width = `${w}px`;
style.height = `${h}px`;
}
el.width = w * dpr;
el.height = h * dpr;
}
if (art.renderOnResize) art.render();
}
else {
style.width = `${w}px`;
style.height = (h) ? `${h}px` : AUTO;
}
}
// update handle/transformOrigin
if (art.dirtyTransformOrigin) {
art.dirtyTransformOrigin = false;
style.transformOrigin = art.currentTransformOriginString;
}
// update transform
if (art.dirtyTransform) {
art.dirtyTransform = false;
style.transform = art.currentTransformString;
}
// update visibility
if (art.dirtyVisibility) {
art.dirtyVisibility = false;
style.display = (art.visibility) ? BLOCK : NONE;
}
// update stampOrder
if (art.dirtyStampOrder) {
art.dirtyStampOrder = false;
style.zIndex = art.stampOrder;
}
}
}
}
}
releaseArray(myartefacts);
}
};
export const recalculateFonts = (delay = 100) => {
setTimeout(() => {
entitynames.forEach(name => {
const ent = entity[name];
if (FONT_USERS.includes(ent.type)) ent.recalculateFont();
});
}, delay);
};