• 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 211

    EnhancedLabel entity - keyboard navigation; hit tests

  • §

    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}`;
  • §

    User interaction: accessibility - keyboard

    canvas.set({
        includeInTabNavigation: true,
    });
    
    let startIndex = -1,
        endIndex = -1,
        cursorIndex = 0,
        selectionInProgress = false;
    
    const clearSelection = () => {
    
        startIndex = -1;
        endIndex = -1;
        cursorIndex = 0;
        selectionInProgress = false;
        updateTextUnits(true);
    };
    
    const updateSelection = () => {
    
        startIndex = cursorIndex;
        endIndex = cursorIndex;
        selectionInProgress = !selectionInProgress;
        updateTextUnits();
    };
    
    const moveCursor = (direction) => {
    
        const maxIndex = mylabel.get('textUnits').length - 1;
    
        switch (direction) {
    
            case 'backwards' :
    
                if (cursorIndex > 0) --cursorIndex;
    
                break;
    
            case 'forwards' :
                if (cursorIndex < maxIndex) ++cursorIndex;
                break;
        }
    
        if (selectionInProgress) endIndex = cursorIndex;
    
        updateTextUnits();
    };
    
    const copySelectionToConsole = () => {
    
        const units = mylabel.get('textUnits'),
            maxIndex = units.length - 1;
    
        if (startIndex >= 0 && startIndex <= maxIndex && endIndex >= 0 && endIndex <= maxIndex) {
    
            const starts = (startIndex > endIndex) ? endIndex : startIndex,
                ends = (startIndex > endIndex) ? startIndex : endIndex;
    
            let text = '';
    
            for (let i = starts; i <= ends; i++) {
    
                text += units[i].chars;
            }
            console.log(`Text to copy: [${text}]`);
        }
        else console.log('Nothing to copy');
    }
    
    scrawl.makeKeyboardZone({
    
        zone: canvas,
    
        ctrlOnly: {
            c: () => copySelectionToConsole(),
        },
    
        none: {
            Backspace: () => clearSelection(),
            Enter: () => updateSelection(),
            ArrowLeft: () => moveCursor('backwards'),
            ArrowUp: () => moveCursor('backwards'),
            ArrowRight: () => moveCursor('forwards'),
            ArrowDown: () => moveCursor('forwards'),
        },
    });
    
    const cursorHighlight = {
        localStyle: {
            includeHighlight: true,
            highlightStyle: 'black',
            fillStyle: 'yellow',
        }
    };
    
    const selectionHighlight = {
        localStyle: {
            includeHighlight: true,
            highlightStyle: 'orange',
        }
    };
    
    const noHighlight = {
        localStyle: null,
    };
    
    const updateTextUnits = (clear = false) => {
    
        mylabel.setAllTextUnits(noHighlight);
    
        if (!clear) {
    
            const maxIndex = mylabel.get('textUnits').length - 1;
    
            if (startIndex >= 0 && startIndex <= maxIndex && endIndex >= 0 && endIndex <= maxIndex) {
    
                const starts = (startIndex > endIndex) ? endIndex : startIndex,
                    ends = (startIndex > endIndex) ? startIndex : endIndex;
    
                for (let i = starts; i <= ends; i++) {
    
                    mylabel.setTextUnit(i, selectionHighlight);
                }
            }
    
            if (cursorIndex >= 0 && cursorIndex <= maxIndex) mylabel.setTextUnit(cursorIndex, cursorHighlight);
        }
    };
  • §

    Create entitys

    const westernText = '<span class="underline">Lorem</span> ipsum <b>dolor sit</b> amet, con&shy;sectetur 😀 adi&shy;piscing &eacute;lit, sed <s>do eius-mod</s> <u>tempoj yn&shy;figizqunt</u> ut <span class="stamp-outlined">labore et dolore</span> <span class="green-highlight">magna aliqua.</span> Ut enim ad <span class="bold">minim veniam,</span> quis <span class="letter-spaced">nostrud</span> exercit-ation <span class="strike">ullamco laboris</span> nisi ut aliquip ex ea <span class="make-monospace">"commodo"</span> consequat. Duis <em>(aute irure d&ouml;lor)</em> in reprehenderit 🤖&icirc;n <i>voluptate</i> velit &copy;2024 <i>esse &lt;cillum&gt; <b>dolore</b> eu fug🎻iat nulla</i> pariatur. <span class="red">Excepteur sint</span> occaecat &iexcl;cupidatat! <strong>non proident,</strong> <span class="word-spaced">sunt in culpa qui</span> offici&thorn;a deserunt <span class="make-bigger"><span class="green-highlight">mollit</span> anim</span> id est laborum.';
    
    scrawl.makeSpiral({
        name: name('spiral-track'),
        strokeStyle: 'rgb(0 0 0 / 0.2)',
        method: 'draw',
        start: ['center', 'center'],
        handle: ['center', 'center'],
        loops: 5,
        loopIncrement: 50,
        drawFromLoop: 2,
        scaleOutline: false,
        useAsPath: true,
    });
    
    const mylabel = scrawl.makeEnhancedLabel({
    
        name: name('my-label'),
        fontString: '16px "Roboto Sans"',
        text: westernText,
    
        layoutTemplate: name('spiral-track'),
        useLayoutTemplateAsPath: true,
    
        textHandle: ['center', 'alphabetic'],
    
        breakTextOnSpaces: false,
    
        checkHitUseTemplate: false,
    
        delta: {
            pathPosition: 0.0005,
        },
        noDeltaUpdates: true,
    });
  • §

    User interaction: mouse/touch

    const checkMouseHover = () => {
    
        const hit = mylabel.checkHit(canvas.here);
    
        if (hit && typeof hit !== 'boolean' && hit.index != null) {
    
            if (selectionInProgress) endIndex = hit.index;
            else cursorIndex = hit.index;
    
            updateTextUnits();
        }
    };
    
    scrawl.addListener('down', () => {
    
        updateTextUnits(true);
    
        const hit = mylabel.checkHit(canvas.here);
    
        if (hit && typeof hit !== 'boolean' && hit.index != null) {
    
            cursorIndex = hit.index;
            startIndex = cursorIndex;
            endIndex = cursorIndex;
            selectionInProgress = true;
        }
        else {
    
            cursorIndex = -1;
            startIndex = -1;
            endIndex = -1;
            selectionInProgress = false;
        }
    
    }, canvas.domElement);
    
    scrawl.addListener(['up', 'leave'], () => {
    
        selectionInProgress = false;
    
    }, canvas.domElement);
  • §

    Scene animation

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

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

    Create the Display cycle animation

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

    User form interaction

    Setup form

    const dom = scrawl.initializeDomInputs([
        ['input', 'alignment', '0'],
        ['input', 'letterSpacing', '0'],
        ['input', 'roll', '0'],
        ['input', 'scale', '1'],
        ['input', 'wordSpacing', '0'],
        ['select', 'animation', 0],
        ['select', 'breakTextOnSpaces', 1],
        ['select', 'font', 0],
        ['select', 'textUnitFlow', 0],
        ['select', 'flipReverse', 0],
        ['select', 'flipUpend', 0],
        ['select', 'alignTextUnitsToPath', 1],
    ]);
    
    
    const updateAnimation = (event) => {
    
        const val = event.target.value;
    
        if (val) mylabel.set({ noDeltaUpdates: false });
        else mylabel.set({ noDeltaUpdates: true });
    };
    scrawl.addNativeListener('change', (e) => updateAnimation(e), dom.animation);
    
    
    const updateFont = (event) => {
    
        const font = event.target.value;
    
        if (font) {
    
            switch (font) {
    
                case 'roboto-serif' :
                    mylabel.set({
                        fontString: '16px "Roboto Serif"',
                        text: westernText,
                        direction: 'ltr',
                    });
                    break;
    
                case 'noto-hebrew-serif' :
                    mylabel.set({
                        fontString: '16px "Noto Hebrew Serif"',
                        text: 'כל אדם זכאי לחירות הדעה והבטוי, לרבות החירות להחיק בדעות ללא כל הפרעה, ולבקש ידיעות ודעות, ולקבלן ולמסרן בכל הדרכים וללא סייגי גבולות כל אדם, כחבר החברה, זכאי לבטחון סוציאלי וזכאי לתבוע שהזכויות הכלכליות הסוציאליות והתרבותיות, שהן חיוניות לכבודו כאדם ולהתפתחות החופשית של אישיותו, יובטחו במשמץ לאומי ובשיתוף פעולה בינלאומי בהתאם לארגונה ולאוצרותיה של המדינה כל אדם זכאי למנוחה ולפנאי',
                        direction: 'rtl',
                    });
                    break;
    
                case 'noto-japanese-sans' :
                    mylabel.set({
                        fontString: '16px "Noto Japanese Sans"',
                        text: '人類社会のすべての構成員の固有の尊厳と平等で譲ることのできない権利とを承認することは&#x2060;、世界における自由&#x2060;、正義及び平和の基礎であるので&#x2060;、 人権の無視及び軽侮が&#x2060;、人類の良心を踏みにじった野蛮行為をもたらし&#x2060;、言論及び信仰の自由が受けられ&#x2060;、恐怖及び欠乏のない世界の到来が&#x2060;、一般の人々の最高の願望として宣言されたので&#x2060;、 人間が専制と圧迫とに対する最後の手段として反逆に訴えることがないようにするためには&#x2060;、法の支配によって人権を保護することが肝要であるので&#x2060;、 諸国間の友好関係の発展を促進することが肝要であるので&#x2060;、',
                        direction: 'ltr',
                    });
                    break;
            }
        }
    };
    scrawl.addNativeListener('change', (e) => updateFont(e), dom.font);
    
    
    scrawl.makeUpdater({
    
        event: ['input', 'change'],
        origin: '.controlItem',
    
        target: mylabel,
    
        useNativeListener: true,
        preventDefault: true,
    
        updates: {
    
            letterSpacing: ['letterSpacing', 'px'],
            wordSpacing: ['wordSpacing', 'px'],
    
            roll: ['roll', 'float'],
            scale: ['scale', 'float'],
    
            textUnitFlow: ['textUnitFlow', 'raw'],
    
            alignment: ['alignment', 'float'],
    
            alignTextUnitsToPath: ['alignTextUnitsToPath', 'boolean'],
            breakTextOnSpaces: ['breakTextOnSpaces', 'boolean'],
    
            flipReverse: ['flipReverse', 'boolean'],
            flipUpend: ['flipUpend', 'boolean'],
        },
    
        callback: clearSelection,
    });
  • §

    Development and testing

    console.log(scrawl.library);