• Jump To … +
    ./source/asset-management/image-asset.js ./source/asset-management/noise-asset.js ./source/asset-management/raw-asset.js ./source/asset-management/reaction-diffusion-asset.js ./source/asset-management/sprite-asset.js ./source/asset-management/video-asset.js ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/snippets.js ./source/core/user-interaction.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/button.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/enhanced-label.js ./source/factory/filter.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/label.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/oval.js ./source/factory/particle-force.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/radial-gradient.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/wheel.js ./source/helper/array-pool.js ./source/helper/color-engine.js ./source/helper/document-root-elements.js ./source/helper/filter-engine-bluenoise-data.js ./source/helper/filter-engine.js ./source/helper/random-seed.js ./source/helper/shape-path-calculation.js ./source/helper/shared-vars.js ./source/helper/system-flags.js ./source/helper/utilities.js ./source/helper/workstore.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/button.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/hidden-dom-elements.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/styles.js ./source/mixin/text.js ./source/mixin/tween.js ./source/scrawl.js ./source/untracked-factory/cell-fragment.js ./source/untracked-factory/coordinate.js ./source/untracked-factory/drag-zone.js ./source/untracked-factory/keyboard-zone.js ./source/untracked-factory/observe-update.js ./source/untracked-factory/palette.js ./source/untracked-factory/particle-history.js ./source/untracked-factory/quaternion.js ./source/untracked-factory/state.js ./source/untracked-factory/text-style.js ./source/untracked-factory/vector.js
  • §

    Core user interaction

    A set of functions that are closely tied to the core/document.js functionality, and a couple of additional coder convenience functions.

    Scrawl-canvas adds some event listeners (mouse movement, screen resize, scrolling) to the window object. These help maintain a centralizerd mouse/touch cursor tracking facility that updates here and then cascades and localizes to artefacts (stacks, canvases and element wrapper objects) which need to keep track of a local, immediately updated mouse/touch coordinate.

    Checks to see if events have occurred happen once per requestAnimationFrame tick - this is to choke the more eager event listeners which can, at times, fire thousands of times a second.

  • §

    Imports

    import * as library from "./library.js";
    
    import { isa_obj, λnull } from "../helper/utilities.js";
    
    import { addListener } from "./events.js";
    
    import { makeAnimation } from "../factory/animation.js";
    
    import { getTrackMouse, setTrackMouse, getMouseChanged, setMouseChanged, getViewportChanged, setViewportChanged, getPrefersContrastChanged, setPrefersContrastChanged, getPrefersReducedMotionChanged, setPrefersReducedMotionChanged, getPrefersDarkColorSchemeChanged, setPrefersDarkColorSchemeChanged, getPrefersReduceTransparencyChanged, setPrefersReduceTransparencyChanged, getPrefersReduceDataChanged, setPrefersReduceDataChanged, getPrefersInvertedColorsChanged, setPrefersInvertedColorsChanged, getPrefersForcedColorsChanged, setPrefersForcedColorsChanged } from '../helper/system-flags.js';
  • §

    Shared constants

    import { _floor, _isFinite, _now, _round, _values, ADD_EVENT_LISTENER, DISPLAY_P3, FONT_USERS, MOUSE, MOUSE_DOWN, MOUSE_ENTER, MOUSE_LEAVE, MOUSE_MOVE, MOUSE_UP, MOVE, POINTER_DOWN, POINTER_ENTER, POINTER_LEAVE, POINTER_MOVE, POINTER_UP, REMOVE_EVENT_LISTENER, SRGB, T_CANVAS, TOUCH_CANCEL, TOUCH_END, TOUCH_MOVE, TOUCH_START } from '../helper/shared-vars.js'
  • §

    Local constants

    const CHANGE = 'change',
        POINTER = 'pointer',
        RESIZE = 'resize',
        SCROLL = 'scroll',
        TOUCH = 'touch';
  • §

    Exported array (to modules). DOM element wrappers subscribe for updates by adding themselves to the uiSubscribedElements array. When an event fires, the updated data will be pushed to them automatically

    export const uiSubscribedElements = [];
  • §

    Exported object (to modules and the scrawl object). The currentCorePosition object holds the global mouse cursor position, alongside browser view dimensions and scroll position

    export const currentCorePosition = {
        x: 0,
        y: 0,
        scrollX: 0,
        scrollY: 0,
        w: 0,
        h: 0,
        type: MOUSE,
        prefersReducedMotion: false,
        prefersDarkColorScheme: false,
        prefersReduceTransparency: false,
        prefersContrast: false,
        prefersReduceData: false,
        prefersInvertedColors: false,
        prefersForcedColors: false,
        displaySupportsP3Color: false,
        canvasSupportsP3Color: false,
        devicePixelRatio: 0,
        rawTouches: [],
    };
  • §

    Accessibility preferences

  • §

    contrastMediaQuery - real-time check on the prefers-contrast user preference, as set for the device or OS

    const contrastMediaQuery = window.matchMedia("(prefers-contrast: more)");
    
    contrastMediaQuery.addEventListener(CHANGE, () => {
    
        const res = contrastMediaQuery.matches;
    
        if (currentCorePosition.prefersContrast !== res) {
    
            currentCorePosition.prefersContrast = res;
            setPrefersContrastChanged(true);
        }
    });
    currentCorePosition.prefersContrast = contrastMediaQuery.matches;
  • §

    reducedMotionMediaQuery - real-time check on the prefers-reduced-motion user preference, as set for the device or OS

    const reducedMotionMediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
    
    reducedMotionMediaQuery.addEventListener(CHANGE, () => {
    
        const res = reducedMotionMediaQuery.matches;
    
        if (currentCorePosition.prefersReducedMotion !== res) {
    
            currentCorePosition.prefersReducedMotion = res;
            setPrefersReducedMotionChanged(true);
        }
    });
    currentCorePosition.prefersReducedMotion = reducedMotionMediaQuery.matches;
  • §

    colorSchemeMediaQuery - real-time check on the prefers-color-scheme user preference, as set for the device or OS

    const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    
    colorSchemeMediaQuery.addEventListener(CHANGE, () => {
    
        const res = colorSchemeMediaQuery.matches;
    
        if (currentCorePosition.prefersDarkColorScheme !== res) {
    
            currentCorePosition.prefersDarkColorScheme = res;
            setPrefersDarkColorSchemeChanged(true);
        }
    });
    currentCorePosition.prefersDarkColorScheme = colorSchemeMediaQuery.matches;
  • §

    reducedTransparencyMediaQuery - real-time check on the prefers-reduced-transparency user preference, as set for the device or OS

    const reducedTransparencyMediaQuery = window.matchMedia("(prefers-reduced-transparency: reduce)");
    
    reducedTransparencyMediaQuery.addEventListener(CHANGE, () => {
    
        const res = reducedTransparencyMediaQuery.matches;
    
        if (currentCorePosition.prefersReduceTransparency !== res) {
    
            currentCorePosition.prefersReduceTransparency = res;
            setPrefersReduceTransparencyChanged(true);
        }
    });
    currentCorePosition.prefersReduceTransparency = reducedTransparencyMediaQuery.matches;
  • §

    reducedDataMediaQuery - real-time check on the prefers-reduced-data user preference, as set for the device or OS

    const reducedDataMediaQuery = window.matchMedia("(prefers-reduced-data: reduce)");
    
    reducedDataMediaQuery.addEventListener(CHANGE, () => {
    
        const res = reducedDataMediaQuery.matches;
    
        if (currentCorePosition.prefersReduceData !== res) {
    
            currentCorePosition.prefersReduceData = res;
            setPrefersReduceDataChanged(true);
        }
    });
    currentCorePosition.prefersReduceData = reducedDataMediaQuery.matches;
  • §

    invertedColorsMediaQuery - real-time check on the inverted-colors user preference, as set for the device or OS

    const invertedColorsMediaQuery = window.matchMedia("(inverted-colors: inverted)");
    
    invertedColorsMediaQuery.addEventListener(CHANGE, () => {
    
        const res = invertedColorsMediaQuery.matches;
    
        if (currentCorePosition.prefersInvertedColors !== res) {
    
            currentCorePosition.prefersInvertedColors = res;
            setPrefersInvertedColorsChanged(true);
        }
    });
    currentCorePosition.prefersInvertedColors = invertedColorsMediaQuery.matches;
  • §

    forcedColorsMediaQuery - real-time check on the forced-colors user preference, as set for the device or OS

    const forcedColorsMediaQuery = window.matchMedia("(forced-colors: active)");
    
    forcedColorsMediaQuery.addEventListener(CHANGE, () => {
    
        const res = forcedColorsMediaQuery.matches;
    
        if (currentCorePosition.prefersForcedColors !== res) {
    
            currentCorePosition.prefersForcedColors = res;
            setPrefersForcedColorsChanged(true);
        }
    });
    currentCorePosition.prefersForcedColors = forcedColorsMediaQuery.matches;
  • §

    Watch for changes when user drags browser window between screens

    displaySupportsP3ColorMediaQuery - real-time check on whether the browser is being displayed on a device screen which supports wide gamut colors

    const displaySupportsP3ColorMediaQuery = window.matchMedia("(color-gamut: p3)");
    
    displaySupportsP3ColorMediaQuery.addEventListener(CHANGE, () => {
    
        const res = displaySupportsP3ColorMediaQuery.matches;
    
        if (currentCorePosition.displaySupportsP3Color !== res) {
    
            currentCorePosition.displaySupportsP3Color = res;
        }
    });
    currentCorePosition.displaySupportsP3Color = displaySupportsP3ColorMediaQuery.matches;
  • §

    Also check to see if the canvas supports DisplayP3 - this is a one-time check as support is device/screen independent

    const checkCanvasSupportsDisplayP3 = () => {
    
        const c = document.createElement("canvas");
  • §

    Needs to be done in try-catch because (apparently) Safari throws a fit if colorSpace option is supported by the canvas engine but the minimum macOS/iOS system requirements for display-p3 support are not met

        try {
    
            const e = c.getContext("2d", { colorSpace: DISPLAY_P3 });
            return e.getContextAttributes().colorSpace === DISPLAY_P3;
        }
        catch { console.log('checkCanvasSupportsDisplayP3 errored')}
        return false;
    };
    currentCorePosition.canvasSupportsP3Color = checkCanvasSupportsDisplayP3();
  • §

    Monitoring the device pixel ratio

    DPR is detected here, but mainly handled in the factory/cell.js file

    • We scale the cell by DPR - this should be the only time we touch native scale functionality!
    • All the other scaling functionality in SC is handled by computiation - applying the scaling factor to dimensions, start, handle, offset etc values which then get saved in the current equivalent attributes

    getPixelRatio - exported to the API

    export const getPixelRatio = () => currentCorePosition.devicePixelRatio;
  • §

    getIgnorePixelRatio, setIgnorePixelRatio - exported to the API

    let ignorePixelRatio = false;
    export const getIgnorePixelRatio = () => ignorePixelRatio;
    export const setIgnorePixelRatio = (val) => ignorePixelRatio = val;
  • §

    setPixelRatioChangeAction - exported to the API

    let pixelRatioChangeAction = λnull;
    export const setPixelRatioChangeAction = (func) => pixelRatioChangeAction = func;
    
    const updatePixelRatio = () => {
    
        const dpr = window.devicePixelRatio;
        currentCorePosition.devicePixelRatio = dpr;
    
        _values(library.canvas).forEach(v => v.dirtyDimensions = true);
        _values(library.cell).forEach(v => v.dirtyDimensions = true);
        _values(library.entity).forEach(v => v.dirtyHost = true);
    
        if (!ignorePixelRatio) pixelRatioChangeAction();
  • §

    We use a one-time media query for checking when the device pixel ratio changes

    • unlike the user preferences media queries, device pixel ratio can be a number of different values
    • we check to see if dpr changes away from the current dpr value
    • then we create a replacement one-time media query to check for changes away from the new value
        matchMedia(`(resolution: ${dpr}dppx)`).addEventListener(CHANGE, updatePixelRatio, { once: true });
    };
    
    updatePixelRatio();
  • §

    Watch for browser window resize, or device rotation, which trigger changes in the viewport dimensions

  • §

    resizeAction function - to check if a view resize has occurred; if yes, flag that currentCorePosition object needs to be updated

    const resizeAction = function () {
    
        const w = document.documentElement.clientWidth,
            h = document.documentElement.clientHeight;
    
        if (currentCorePosition.w !== w || currentCorePosition.h !== h) {
    
            currentCorePosition.w = w;
            currentCorePosition.h = h;
            setMouseChanged(true);
            setViewportChanged(true);
        }
    };
  • §

    Watch for scrolling interactions

  • §

    scrollAction function - to check if a view scroll has occurred; if yes, flag that currentCorePosition object needs to be updated

    const scrollAction = function () {
    
        const x = window.pageXOffset,
            y = window.pageYOffset;
    
        if (currentCorePosition.scrollX !== x || currentCorePosition.scrollY !== y) {
            currentCorePosition.x += (x - currentCorePosition.scrollX);
            currentCorePosition.y += (y - currentCorePosition.scrollY);
            currentCorePosition.scrollX = x;
            currentCorePosition.scrollY = y;
            setMouseChanged(true);
        }
    };
  • §

    Watch for mouse, pointer and touch movement

  • §

    moveAction function - to check if mouse cursor position has changed; if yes, update currentCorePosition object and flag that the updated needs to cascade to subscribed elements at the next RAF invocation.

  • §

    The events that trigger this function (pointermove, pointerup, pointerdown, pointerleave, pointerenter; or mousemove, mouseup, mousedown, mouseleave, mouseenter) are tied to the window object, not to any particular DOM element.

    const moveAction = function (e) {
    
        const x = _round(e.pageX),
            y = _round(e.pageY);
    
        if (currentCorePosition.x !== x || currentCorePosition.y !== y) {
            currentCorePosition.type = (navigator.pointerEnabled) ? POINTER : MOUSE;
            currentCorePosition.x = x;
            currentCorePosition.y = y;
            setMouseChanged(true);
        }
    };
  • §

    touchAction function - maps touch coordinates to the mouse cursor position (via currentCorePosition) and then immediately invokes a cascade update action to all subscribed elements.

  • §

    The events that trigger this function (touchstart, touchmove, touchend, touchcancel) are tied to the window object, not to any particular DOM element.

  • §

    Note: this is different to mouse moveAction, which is choked via an animation object so update checks happen on each requestAnimationFrame.

    TODO: Need to keep an eye on how many times touchAction gets run, for example during a touch-driven drag-and-drop action. If necessary, add a Date.now mediated choke to the check (say minimum 15ms between checks?) to minimize impact on the wider Scrawl-canvas ecosystem.

    let touchActionLastChecked = 0,
        touchActionChoke = 16;
    
    export const getTouchActionChoke = function () {
    
        return touchActionChoke;
    };
    
    export const setTouchActionChoke = function (val) {
    
        if (_isFinite(val)) touchActionChoke = val;
    };
    
    export const touchAction = function (e, resetCoordsToZeroOnTouchEnd = true) {
    
        currentCorePosition.rawTouches.length = 0;
    
        if (e.touches && e.touches.length) {
    
            currentCorePosition.rawTouches.push(...e.touches);
    
            const touch = e.touches[0],
                x = _round(touch.pageX),
                y = _round(touch.pageY);
    
            if (currentCorePosition.x !== x || currentCorePosition.y !== y) {
    
                currentCorePosition.type = TOUCH;
                currentCorePosition.x = x;
                currentCorePosition.y = y;
            }
        }
        else {
    
            currentCorePosition.type = TOUCH;
    
            if (resetCoordsToZeroOnTouchEnd) {
    
                currentCorePosition.x = 0;
                currentCorePosition.y = 0;
            }
        }
    
        const now = _now();
    
        if (now > touchActionLastChecked + touchActionChoke) {
    
            touchActionLastChecked = now;
            updateUiSubscribedElements();
        }
    };
  • §

    Cascade interaction results down to subscribed elements

  • §

    Functions to update uiSubscribedElements attached to specified DOM elements. Each stack or canvas element tracked by Scrawl-canvas will include a local here object which includes details of the element’s current dimensions, relative position, and the position of the mouse cursor in relation to its top-left corner. These all need to be updated whenever there’s a resize, scroll or cursor movement.

    const updateUiSubscribedElements = function () {
    
        for (let i = 0, iz = uiSubscribedElements.length; i < iz; i++) {
    
            updateUiSubscribedElement(uiSubscribedElements[i]);
        }
    };
    
    const updateUiSubscribedElement = function (art) {
    
        const dom = library.artefact[art];
    
        if (dom) {
    
            const { here, domElement:el } = dom;
  • §

    Accessibility

            here.prefersContrast = currentCorePosition.prefersContrast;
            here.prefersReducedMotion = currentCorePosition.prefersReducedMotion;
            here.prefersDarkColorScheme = currentCorePosition.prefersDarkColorScheme;
            here.prefersReduceTransparency = currentCorePosition.prefersReduceTransparency;
            here.prefersReduceData = currentCorePosition.prefersReduceData;
            here.prefersInvertedColors = currentCorePosition.prefersInvertedColors;
            here.prefersForcedColors = currentCorePosition.prefersForcedColors;
            here.devicePixelRatio = currentCorePosition.devicePixelRatio;
    
            if (getPrefersContrastChanged()) dom.contrastActions();
            if (getPrefersReducedMotionChanged()) dom.reducedMotionActions();
            if (getPrefersDarkColorSchemeChanged()) dom.colorSchemeActions();
            if (getPrefersReduceTransparencyChanged()) dom.reducedTransparencyActions();
            if (getPrefersReduceDataChanged()) dom.reducedDataActions();
            if (getPrefersInvertedColorsChanged()) dom.invertedColorsActions();
            if (getPrefersForcedColorsChanged()) dom.forcedColorsActions();
  • §

    DOM-element-dependant values

            if (el) {
    
                const dims = el.getBoundingClientRect(),
                    dox = _round(dims.left + window.pageXOffset),
                    doy = _round(dims.top + window.pageYOffset),
                    dot = dims.top,
                    doh = dims.height,
                    wih = window.innerHeight;
    
                here.w = _round(dims.width);
                here.h = _round(doh);
    
                here.type = currentCorePosition.type;
  • §

    Position of the artefact in the browser/device viewport

                const ivpt = dot / wih,
                    ivpb = (dot + doh) / wih,
                    ivpc = (ivpt + ivpb) / 2;
    
                here.inViewportTop = ivpt;
                here.inViewportBase = ivpb;
                here.inViewportCenter = ivpc;
  • §

    Default mouse tracking behaviour

                if (!dom.localMouseListener) {
    
                    here.localListener = false;
                    here.active = true;
    
                    here.x = _round(currentCorePosition.x - dox);
                    here.y = _round(currentCorePosition.y - doy);
    
                    here.normX = (here.w) ? here.x / here.w : false;
                    here.normY = (here.h) ? here.y / here.h : false;
                    here.offsetX = dox;
                    here.offsetY = doy;
    
                    if (here.normX < 0 || here.normX > 1 || here.normY < 0 || here.normY > 1) here.active = false;
                }
  • §

    DOM-based artefacts have the option of creating a local mouse move event listener

    • The listener better tracks mouse movements across the artefact when its element has been rotated in three dimensions.
    • The listener will update the local here.x and here.y values
                else {
    
                    here.localListener = true;
                    here.active = false;
    
                    here.normX = (here.originalWidth) ? here.x / here.originalWidth : false;
                    here.normY = (here.originalHeight) ? here.y / here.originalHeight : false;
                    here.offsetX = dox;
                    here.offsetY = doy;
    
                    if (here.x > dom.activePadding && here.x < here.originalWidth - dom.activePadding && here.y > 0 + dom.activePadding && here.y < here.originalHeight - dom.activePadding) here.active = true;
                }
    
                const touches = currentCorePosition.rawTouches;
    
                if (!here.touches) here.touches = [];
    
                if (touches.length) {
    
                    here.touches.length = 0;
    
                    for (let i = 0, iz = touches.length; i < iz; i++) {
    
                        const touch = touches[i];
    
                        here.touches.push([_round(touch.pageX - dox), _round(touch.pageY - doy)]);
                    }
                }
  • §

    Canvas fit attribute adjustments

                if (dom.type === T_CANVAS) dom.updateBaseHere(here, dom.fit);
  • §

    Automatically check for element resize

    • The artefact’s checkForResize flag needs to be set
    • We ignore resizing actions while dimensions-related dirty flags are set (to prevent getting ourselves into a continuous feedback loop)
                if (dom.checkForResize && !dom.dirtyDimensions && !dom.dirtyDomDimensions) {
    
                    const [w, h] = dom.currentDimensions;
    
                    if (dom.type === T_CANVAS) {
  • §

    Regardless of the setting of <canvas> element’s boxSizing style attribute:

    • It will include padding and borders in its getBoundingClientRect object (and its getComputedStyle width/height values), but these are specifically excluded from the element’s width and height attributes
    • Which leads to the normal resize test - if (w !== here.w || h !== here.h) - triggering on every mouse/scroll/resize event, which in turn leads to the canvas dimensions increasing uncontrollably.
    • Solved by subtracting padding/border values from the getBoundingClientRect dimension values before performing the test.
                        const s = dom.computedStyles,
                            hw = _floor(here.w - parseFloat(s.borderLeftWidth) - parseFloat(s.borderRightWidth) - parseFloat(s.paddingLeft) - parseFloat(s.paddingRight)),
                            hh = _floor(here.h - parseFloat(s.borderTopWidth) - parseFloat(s.borderBottomWidth) - parseFloat(s.paddingTop) - parseFloat(s.paddingBottom));
    
                        if (w !== hw || h !== hh) {
    
                            dom.set({
    
                                dimensions: [hw, hh],
                            });
                        }
                    }
                    else {
  • §

    Stack and Element artefacts resize test.

    • Tested in Demo DOM-011.
                        if (w !== here.w || h !== here.h) {
    
                            dom.set({
    
                                dimensions: [here.w, here.h],
                            });
                        }
                    }
                }
            }
        }
    };
    
    const updateTextBasedEntitys = function () {
    
        _values(library.entity).forEach(ent => {
    
            if (FONT_USERS.includes(ent.type)) ent.recalculateFont(true);
        });
    };
  • §

    Internal functions that get triggered when setting a DOM-based artefact’s trackHere attribute. They add/remove an event listener to the artefact’s domElement.

    export const addLocalMouseMoveListener = function (wrapper) {
    
        if (isa_obj(wrapper)) {
    
            if (wrapper.localMouseListener) wrapper.localMouseListener();
    
            if (!wrapper.here) wrapper.here = {};
    
            wrapper.here.originalWidth = wrapper.currentDimensions[0];
            wrapper.here.originalHeight = wrapper.currentDimensions[1];
    
            wrapper.localMouseListener = addListener(MOVE, function (e) {
    
                if (wrapper.here) {
    
                    wrapper.here.x = _round(parseFloat(e.offsetX));
                    wrapper.here.y = _round(parseFloat(e.offsetY));
                }
            }, wrapper.domElement);
        }
    };
    
    export const removeLocalMouseMoveListener = function (wrapper) {
    
        if (isa_obj(wrapper)) {
    
            if (wrapper.localMouseListener) wrapper.localMouseListener();
    
            wrapper.localMouseListener = false;
        }
    };
  • §

    Animation object which checks whether any window event listeners have fired, and actions accordingly

    const coreListenersTracker = makeAnimation({
    
        name: 'SC-core-listeners-tracker',
        order: 0,
        delay: true,
        fn: function () {
    
            const trackMouse = getTrackMouse();
            const mouseChanged = getMouseChanged();
            const viewportChanged = getViewportChanged();
            const prefersContrastChanged = getPrefersContrastChanged();
            const prefersReducedMotionChanged = getPrefersReducedMotionChanged();
            const prefersDarkColorSchemeChanged = getPrefersDarkColorSchemeChanged();
            const prefersReduceTransparencyChanged = getPrefersReduceTransparencyChanged();
            const prefersReduceDataChanged = getPrefersReduceDataChanged();
            const prefersInvertedColorsChanged = getPrefersInvertedColorsChanged();
            const prefersForcedColorsChanged = getPrefersForcedColorsChanged();
    
            if ((trackMouse && mouseChanged) ||
                prefersReducedMotionChanged ||
                prefersContrastChanged ||
                prefersDarkColorSchemeChanged ||
                prefersReduceTransparencyChanged ||
                prefersReduceDataChanged ||
                prefersInvertedColorsChanged ||
                prefersForcedColorsChanged) updateUiSubscribedElements();
    
            if (trackMouse && mouseChanged) setMouseChanged(false);
            if (prefersContrastChanged) setPrefersContrastChanged(false);
            if (prefersReducedMotionChanged) setPrefersReducedMotionChanged(false);
            if (prefersDarkColorSchemeChanged) setPrefersDarkColorSchemeChanged(false);
            if (prefersReduceTransparencyChanged) setPrefersReduceTransparencyChanged(false);
            if (prefersReduceDataChanged) setPrefersReduceDataChanged(false);
            if (prefersInvertedColorsChanged) setPrefersInvertedColorsChanged(false);
            if (prefersForcedColorsChanged) setPrefersForcedColorsChanged(false);
    
            if (viewportChanged) {
    
                setViewportChanged(false);
  • §

    This is to capture changes in the browser viewport size which can affect Label-related text using a font size measured relative to the viewport size

                updateTextBasedEntitys();
            }
        },
    });
  • §

    Exported functions (to modules and the scrawl object). Event listeners can be a drain on web page efficiency. If a web page contains only static canvas (and/or stack) displays, with no requirement for user interaction, we can minimize Scrawl-canvas’s impact on those pages by switching off the core listeners (and also the core animation loop).

    export const startCoreListeners = function () {
    
        actionCoreListeners(REMOVE_EVENT_LISTENER);
        actionCoreListeners(ADD_EVENT_LISTENER);
    
        setTrackMouse(true);
        setMouseChanged(true);
        coreListenersTracker.run();
    };
    
    export const stopCoreListeners = function () {
    
        setTrackMouse(false);
        setMouseChanged(false);
        coreListenersTracker.halt();
    
        actionCoreListeners(REMOVE_EVENT_LISTENER);
    };
  • §

    Helper function

    const actionCoreListeners = function (action) {
    
        if (navigator.pointerEnabled || navigator.msPointerEnabled) {
    
            window[action](POINTER_MOVE, moveAction, false);
            window[action](POINTER_UP, moveAction, false);
            window[action](POINTER_DOWN, moveAction, false);
            window[action](POINTER_LEAVE, moveAction, false);
            window[action](POINTER_ENTER, moveAction, false);
        }
        else {
    
            window[action](MOUSE_MOVE, moveAction, false);
            window[action](MOUSE_UP, moveAction, false);
            window[action](MOUSE_DOWN, moveAction, false);
            window[action](MOUSE_LEAVE, moveAction, false);
            window[action](MOUSE_ENTER, moveAction, false);
    
            window[action](TOUCH_MOVE, touchAction, {passive: true});
            window[action](TOUCH_START, touchAction, {passive: true});
            window[action](TOUCH_END, touchAction, {passive: true});
            window[action](TOUCH_CANCEL, touchAction, {passive: true});
        }
    
        window[action](SCROLL, scrollAction, {passive: true});
        window[action](RESIZE, resizeAction, false);
    };
  • §

    Exported functions (to modules). Invoke the resize and/or scroll event listeners once, outside the regular requestAnimationFrame tick.

    export const applyCoreResizeListener = function () {
    
        resizeAction();
        setMouseChanged(true);
        setViewportChanged(true);
    };
    
    export const applyCoreScrollListener = function () {
    
        scrollAction();
        setMouseChanged(true);
    };
  • §

    Putting this small library-related function here as this file gets all core/library objects rather than selectively importing some of them

    export const purgeFontMetadata = function () {
    
        const { fontfamilymetadata, fontfamilymetadatanames } = library;
    
        fontfamilymetadatanames.forEach(f => delete fontfamilymetadata[f]);
        fontfamilymetadatanames.length = 0;
    };
  • §

    Wide gamut colors helper

    export const getCanvasColorSpace = (useP3) => {
    
        const { canvasSupportsP3Color, displaySupportsP3Color } = currentCorePosition;
        if (useP3 && canvasSupportsP3Color && displaySupportsP3Color) return DISPLAY_P3;
        return SRGB;
    };