• Jump To … +
    ./demo/canvas-001.js ./demo/canvas-002.js ./demo/canvas-003.js ./demo/canvas-004.js ./demo/canvas-005.js ./demo/canvas-006.js ./demo/canvas-007.js ./demo/canvas-008.js ./demo/canvas-009.js ./demo/canvas-010.js ./demo/canvas-011.js ./demo/canvas-012.js ./demo/canvas-013.js ./demo/canvas-014.js ./demo/canvas-015.js ./demo/canvas-016.js ./demo/canvas-017.js ./demo/canvas-018.js ./demo/canvas-019.js ./demo/canvas-020.js ./demo/canvas-021.js ./demo/canvas-022.js ./demo/canvas-023.js ./demo/canvas-024.js ./demo/canvas-025.js ./demo/canvas-026.js ./demo/canvas-027.js ./demo/canvas-028.js ./demo/canvas-029.js ./demo/canvas-030.js ./demo/canvas-031.js ./demo/canvas-032.js ./demo/canvas-033.js ./demo/canvas-034.js ./demo/canvas-035.js ./demo/canvas-036.js ./demo/canvas-037.js ./demo/canvas-038.js ./demo/canvas-039.js ./demo/canvas-040.js ./demo/canvas-041.js ./demo/canvas-042.js ./demo/canvas-043.js ./demo/canvas-044.js ./demo/canvas-045.js ./demo/canvas-046.js ./demo/canvas-047.js ./demo/canvas-048.js ./demo/canvas-049.js ./demo/canvas-050.js ./demo/canvas-051.js ./demo/canvas-052.js ./demo/canvas-053.js ./demo/canvas-054.js ./demo/canvas-055.js ./demo/canvas-056.js ./demo/canvas-057.js ./demo/canvas-058.js ./demo/canvas-059.js ./demo/canvas-060.js ./demo/canvas-061.js ./demo/canvas-062.js ./demo/canvas-063.js ./demo/canvas-064.js ./demo/canvas-065.js ./demo/canvas-066.js ./demo/canvas-067.js ./demo/canvas-068.js ./demo/canvas-069.js ./demo/canvas-070.js ./demo/canvas-071.js ./demo/canvas-072.js ./demo/canvas-073.js ./demo/canvas-201.js ./demo/canvas-202.js ./demo/canvas-203.js ./demo/canvas-204.js ./demo/canvas-205.js ./demo/canvas-206.js ./demo/canvas-207.js ./demo/canvas-208.js ./demo/canvas-209.js ./demo/canvas-210.js ./demo/canvas-211.js ./demo/canvas-212.js ./demo/delaunator-001.js ./demo/delaunator-002.js ./demo/dom-001.js ./demo/dom-002.js ./demo/dom-003.js ./demo/dom-004.js ./demo/dom-005.js ./demo/dom-006.js ./demo/dom-007.js ./demo/dom-008.js ./demo/dom-009.js ./demo/dom-010.js ./demo/dom-011.js ./demo/dom-012.js ./demo/dom-013.js ./demo/dom-015.js ./demo/dom-016.js ./demo/dom-017.js ./demo/dom-018.js ./demo/dom-019.js ./demo/dom-020.js ./demo/dom-021.js ./demo/filters-001.js ./demo/filters-002.js ./demo/filters-003.js ./demo/filters-004.js ./demo/filters-005.js ./demo/filters-006.js ./demo/filters-007.js ./demo/filters-008.js ./demo/filters-009.js ./demo/filters-010.js ./demo/filters-011.js ./demo/filters-012.js ./demo/filters-013.js ./demo/filters-014.js ./demo/filters-015.js ./demo/filters-016.js ./demo/filters-017.js ./demo/filters-018.js ./demo/filters-019.js ./demo/filters-020.js ./demo/filters-021.js ./demo/filters-022.js ./demo/filters-023.js ./demo/filters-024.js ./demo/filters-025.js ./demo/filters-026.js ./demo/filters-027.js ./demo/filters-028.js ./demo/filters-029.js ./demo/filters-030.js ./demo/filters-031.js ./demo/filters-032.js ./demo/filters-033.js ./demo/filters-034.js ./demo/filters-035.js ./demo/filters-036.js ./demo/filters-037.js ./demo/filters-038.js ./demo/filters-039.js ./demo/filters-040.js ./demo/filters-041.js ./demo/filters-042.js ./demo/filters-101.js ./demo/filters-102.js ./demo/filters-103.js ./demo/filters-104.js ./demo/filters-105.js ./demo/filters-501.js ./demo/filters-502.js ./demo/filters-503.js ./demo/filters-504.js ./demo/filters-505.js ./demo/mediapipe-001.js ./demo/mediapipe-002.js ./demo/mediapipe-003.js ./demo/modules-001.js ./demo/modules-002.js ./demo/modules-003.js ./demo/modules-004.js ./demo/modules-005.js ./demo/modules-006.js ./demo/packets-001.js ./demo/packets-002.js ./demo/particles-001.js ./demo/particles-002.js ./demo/particles-003.js ./demo/particles-004.js ./demo/particles-005.js ./demo/particles-006.js ./demo/particles-007.js ./demo/particles-008.js ./demo/particles-009.js ./demo/particles-010.js ./demo/particles-011.js ./demo/particles-012.js ./demo/particles-013.js ./demo/particles-014.js ./demo/particles-015.js ./demo/particles-016.js ./demo/particles-017.js ./demo/snippets-001.js ./demo/snippets-002.js ./demo/snippets-003.js ./demo/snippets-004.js ./demo/snippets-005.js ./demo/snippets-006.js ./demo/temp-001.js ./demo/temp-shape-scale-investigation.js ./demo/tensorflow-001.js ./demo/tensorflow-002.js ./demo/utilities.js
  • §

    Demo Canvas 027

    Video control and manipulation; chroma-based hit zone

  • §

    Run code

    import * as scrawl from '../source/scrawl.js'
    
    import { reportSpeed } from './utilities.js';
  • §

    Scene setup

    Get a handle to the Canvas wrapper

    const canvas = scrawl.findCanvas('mycanvas');
  • §

    Namespacing boilerplate

    const namespace = canvas.name;
    const name = (n) => `${namespace}-${n}`;
  • §

    Importing the video file programmatically

    const myvideo = scrawl.makePicture({
    
        name: name('test-video'),
    
        videoSource: 'img/swans.mp4',
    
        width: '100%',
        height: '100%',
    
        copyWidth: '100%',
        copyHeight: '100%',
    });
    
    const initialVideoStart = scrawl.addNativeListener(['mouseup', 'touchend', 'keyup'], () => {
    
        myvideo.set({
    
            video_muted: true,
            video_loop: true,
        });
  • §

    Get rid of the event listener after invocation - it’s a one-time-only action

        initialVideoStart();
    
    }, canvas.domElement);
  • §

    canvas-based buttons

    const playPauseAction = function () {
    
        if (myvideo.get('video_paused')) {
    
            playPause.set({ text: 'PAUSE' });
            myvideo.videoPlay();
        }
        else {
    
            playPause.set({ text: 'PLAY' });
            myvideo.videoPause();
        }
    };
    
    const playPause = scrawl.makeLabel({
    
        name: name('play-pause-button'),
        order: 2,
    
        text: 'PLAY',
    
        fontString: '2rem sans-serif',
        letterSpacing: 3,
    
        fillStyle: 'yellow',
    
        startX: '75%',
        handleX: 'center',
        startY: '98%',
        handleY: 'bottom',
    
        underlineWidth: 4,
        underlineOffset: 0.96,
        underlineGap: 0,
    
    /** @this {import('../source/scrawl.js').LabelInstance} */
        onEnter: function () {
    
            canvas.set({
                css: { cursor: 'pointer' }
            });
    
            this.set({ includeUnderline: true });
        },
    
    /** @this {import('../source/scrawl.js').LabelInstance} */
        onLeave: function () {
    
            canvas.set({
                css: { cursor: 'auto' }
            });
    
            this.set({ includeUnderline: false });
        },
  • §

    Accesibility

        textIsAccessible: false,
        button: {
    
            name: name('play-pause-el'),
            description: 'Play | Pause',
            clickAction: playPauseAction,
            tabOrder: 0,
        },
    
        onUp: playPauseAction,
    });
    
    const listenMuteAction = function () {
    
        if (myvideo.get('video_muted')) {
    
            listenMute.set({ text: 'MUTE' });
            myvideo.set({ video_muted: false });
        }
        else {
    
            listenMute.set({ text: 'LISTEN' });
            myvideo.set({ video_muted: true });
        }
    };
    
    const listenMute = playPause.clone({
    
        name: name('listen-mute-button'),
    
        text: 'LISTEN',
    
        startX: '25%',
    
        button: {
    
            name: name('listen-mute-el'),
            description: 'Listen | Mute',
            clickAction: listenMuteAction,
            tabOrder: 1,
        },
    
        onUp: listenMuteAction,
    });
  • §

    Turn the swans pink

    scrawl.makeFilter({
    
        name: name('swan-mask'),
    
        actions: [
            {
                action: 'threshold',
                level: 200,
                low: [0, 0, 0],
                high: [255, 0, 0],
            },
            {
                action: 'channels-to-alpha',
                includeGreen: false,
                includeBlue: false,
            },
        ],
    });
    
    scrawl.makePicture({
    
        name: name('test-swan-image'),
    
        asset: 'swans',
    
        width: '100%',
        height: '55%',
    
        startY: '25%',
    
        copyWidth: '100%',
        copyHeight: '55%',
    
        copyStartY: '25%',
    
        filters: [name('swan-mask')],
    
        globalAlpha: 0.1,
    
    /** @this {import('../source/scrawl.js').PictureInstance} */
        onEnter: function () {
    
            this.set({ globalAlpha: 0.3 });
    
            canvas.set({
                css: {
                    cursor: 'pointer',
                }
            });
        },
    
    /** @this {import('../source/scrawl.js').PictureInstance} */
        onLeave: function () {
    
            this.set({ globalAlpha: 0.1 });
    
            canvas.set({
                css: { cursor: 'auto' }
            });
        },
    
    /** @this {import('../source/scrawl.js').PictureInstance} */
        onUp: function () {
    
            this.clickAnchor();
        },
    
        anchor: {
            name: name('wikipedia-swan-link'),
            href: 'https://en.wikipedia.org/wiki/Swan',
            description: 'Link to the Wikipedia article on swans',
            tabOrder: 2,
        },
    
        checkHitIgnoreTransparency: true,
    });
  • §

    Ticker

    const myticker = scrawl.makeTicker({
    
        name: name('test-video-ticker'),
        duration: '22.55s',
        cycles: 0,
    });
    
    const myLocalTweenFactory = function (name, ticker, target, data) {
    
        for (let i = 0, iz = data.length; i < iz; i++) {
    
            const [start, duration, x0, y0, x1, y1] = data[i];
    
            scrawl.makeTween({
    
                name: `${name}-${i}`,
    
                ticker: ticker,
    
                targets: [target],
    
                time: start,
                duration: duration,
    
                definitions: [
                    {
                        attribute: 'startX',
                        start: x0,
                        end: x1,
                    },
                    {
                        attribute: 'startY',
                        start: y0,
                        end: y1,
                    },
                ],
            });
        }
    };
  • §

    Goose 1

    const mygoose = scrawl.makeBlock({
    
        name: name('test-goose1-hitzone'),
        order: 1,
    
        width: '15%',
        height: '15%',
    
        handleX: 'center',
        handleY: 'center',
    
        lineWidth: 2,
        strokeStyle: 'yellow',
    
        method: 'none',
    
    /** @this {import('../source/scrawl.js').BlockInstance} */
        onEnter: function () {
    
            canvas.set({
                css: { cursor: 'pointer' }
            });
    
            this.set({ method: 'draw' });
        },
    
    /** @this {import('../source/scrawl.js').PictureInstance} */
        onLeave: function () {
    
            canvas.set({
                css: { cursor: 'auto' }
            });
    
            this.set({ method: 'none' });
        },
    
    /** @this {import('../source/scrawl.js').PictureInstance} */
        onUp: function () {
    
            this.clickAnchor();
        },
    
        anchor: {
            name: name('wikipedia-goose-link-1'),
            href: 'https://en.wikipedia.org/wiki/Goose',
            description: 'First link to the Wikipedia article on geese',
            tabOrder: 3,
        },
    });
    
    myLocalTweenFactory(
        name('test-goose1-tween'),
        name('test-video-ticker'),
        name('test-goose1-hitzone'),
        [
            [0, '3s', '27%', '73%', '12%', '66%'],
            ['3s', '3s', '12%', '66%', '-3%', '68%']
        ],
    );
    
    scrawl.makeAction({
    
        name: name('test-goose1-action-show'),
    
        ticker: name('test-video-ticker'),
    
        time: 0,
    
        targets: [name('test-goose1-hitzone')],
    
        action: function () {
    
            this.targets[0].set({
    
                visibility: true,
                anchorDisabled: false,
            });
        },
    
    }).clone({
    
        name: name('test-goose1-action-hide'),
        time: '6s',
    
        action: function () {
    
            this.targets[0].set({
    
                visibility: false,
                anchorDisabled: true,
            });
        },
    });
  • §

    Goose 2

    mygoose.clone({
    
        name: name('test-goose2-hitzone'),
    
        width: '22%',
        height: '16%',
  • §

    It’s generally not a good idea to share <a> anchor link elements between interactive artefacts. However, be aware that repeating a link may not be the best user experience for those users accessing the web page with assistive technologies.

        anchor: {
            name: name('wikipedia-goose-link-2'),
            href: 'https://en.wikipedia.org/wiki/Goose',
            description: 'Second link to the Wikipedia article on geese',
            tabOrder: 4,
        },
    });
  • §

    Test creating Tweens referencing the Ticker object rather than the object’s name string

    myLocalTweenFactory(
        name('test-goose2-tween'),
        myticker,
        name('test-goose2-hitzone'),
        [
            [0,       '4s',   '89%', '89%', '77%', '80%'],
            ['4s',    '4s',   '77%', '80%', '65%', '80%'],
            ['8s',    '2.5s', '65%', '80%', '63%', '74%'],
            ['10.5s', '6s',   '63%', '74%', '43%', '68%'],
            ['16.5s', '3s',   '43%', '68%', '33%', '66%'],
            ['19.5s', '3s',   '33%', '66%', '31%', '64%'],
        ],
    );
    
    scrawl.addListener('move', () => canvas.cascadeEventAction('move'), canvas.domElement);
    scrawl.addNativeListener(['touchstart', 'touchmove'], () => canvas.cascadeEventAction('move'), canvas.domElement);
    scrawl.addNativeListener(['click', 'touchend'], () => canvas.cascadeEventAction('up'), canvas.domElement);
  • §

    Video time bar

    const vtBackground = scrawl.makeBlock({
    
        name: name('test-video-time-background'),
    
        width: '100%',
        height: 10,
    
        fillStyle: 'white',
    });
    
    const vtTime = vtBackground.clone({
    
        name: name('test-video-time-bar'),
    
        width: 0,
        fillStyle: 'red',
    })
    
    const vtLabel = scrawl.makeLabel({
    
        name: name('test-video-time-label'),
        fontString: 'bold 1rem monospace',
        start: [5, 20],
        fillStyle: 'yellow',
  • §

    Accesibility - this Label updates many times per second. As such, we need to mark the DOM element which will hold the label text (for accessibility) with the role of timer.

    • All DOM elements containing accessible label texts are given an aria-live="polite" attribute.
    • Then it will be up to the user’s screen reader software to decide how to announce text changes to the user.
        accessibleTextRole: 'timer',
    });
    
    const videoTimeBar = function () {
    
        let currentVideoTime,
            videoDuration;
    
        return function () {
    
            currentVideoTime = myvideo.get('video_currentTime');
    
            if (!videoDuration) videoDuration = myvideo.get('video_duration');
    
            if (videoDuration) {
    
                vtTime.set({
    
                    width: `${(currentVideoTime * 100) / videoDuration}%`,
                });
    
                vtLabel.set({
    
                    text: ` ${currentVideoTime.toFixed(2)} / ${videoDuration.toFixed(2)}`,
                });
    
                myticker.seekTo(currentVideoTime * 1000, true);
            }
        };
    }();
  • §

    Scene animation

    Function to display frames-per-second data, and other information relevant to the demo

    const report = reportSpeed('#reportmessage');
  • §

    Create the Animation loop which will run the Display cycle

    scrawl.makeRender({
        name: name('animation'),
        target: canvas,
    
        commence: videoTimeBar,
        afterShow: report,
    });
  • §

    Development and testing

    console.log(scrawl.library);