npm.nicfv.com
    Preparing search index...

    Simple Paint

    In this example, we create a simple canvas in which we can paint in different colors, depending on what color is currently selected which mouse button is pressed. Our "paint" is a series of circles that are rendered whenever the mouse button is down and when the mouse is moved. The canvas must also be in focus.

    We use callback functions to accept user mouse input and keyboard input.

    The keyboard instructions are outlined in the index.html file below.

    We also can determine which mouse button was pressed and draw different colors depending on which button was pressed:

    • Left mouse button = Selected color
    • Scroll button = Selected color + 90 degrees
    • Right mouse button = Selected color + 180 degrees (complimentary color)

    We also use some basic customization properties for the canvas such as background, border, and borderBlur to customize the canvas appearance, and all of the drawing logic is inside our callback functions.

    Our canvas has 2 layers. Layer 0 contains the drawing canvas, and layer 1 is for our cursor. We only want to clear layer 1 each frame so we can render the new cursor position, size, and color. We want layer 0 to remain static.

    Anything we need to draw on the canvas must be a class that extends Drawable meaning that it must implement the draw(context) function. We define draw functions within both our Circle and Cursor classes to satisfy this requirement.

    Follow these steps to create a new webpack project and install the graphico dependency to run this example.

    # Create and open project folder
    mkdir Simple_Paint_demo
    cd Simple_Paint_demo
    # Initialize project and install dependencies
    npm init -y
    npm pkg set type="module"
    npm i graphico@1.1.0 webpack-cli
    # Create source files
    touch index.html
    mkdir src
    touch src/index.js
    # Open new files
    open index.html
    open src/index.js

    Copy and paste the following source code blocks into the newly created files.

    <html>

    <head>
    <script type="text/javascript" src="dist/main.js" defer></script>
    </head>

    <body>
    <div id="canvas"></div>
    <ul>
    <li>Use the left/right arrows to cycle through paint colors</li>
    <li>Use the up/down arrows to change brush stroke size</li>
    <li>Use the left/right/middle mouse buttons to paint different colors</li>
    <li>Press <kbd>P</kbd> to take a screenshot</li>
    <li>Press <kbd>O</kbd> to start or stop screen recording</li>
    </ul>
    </body>

    </html>
    import { Canvas } from 'graphico';

    // Mouse button IDs (could possibly be different for your hardware)
    const LCLICK = 0,
    MCLICK = 1,
    RCLICK = 2;

    // The HSL color hue
    let hue = 0;

    // The paint brush radius
    let radius = 3;


    // Return the HSL color code
    function hsl(h = 0, s = 100, l = 50) {
    return `hsl(${h},${s}%,${l}%)`;
    }

    // Extends `Drawable` by implementing the `draw` function.
    class Circle {
    #color;
    #x;
    #y;
    constructor(color = '', x = 0, y = 0) {
    this.#color = color;
    this.#x = x;
    this.#y = y;
    }
    draw(ctx) {
    // Call HTML canvas context rendering 2D functions here using the `ctx` object
    ctx.fillStyle = this.#color;
    ctx.beginPath();
    ctx.arc(this.#x, this.#y, radius, 0, 2 * Math.PI);
    ctx.fill();
    }
    }

    class Cursor {
    #x;
    #y;
    constructor() {
    this.#x = 0;
    this.#y = 0;
    }
    updatePos(x = 0, y = 0) {
    this.#x = x;
    this.#y = y;
    }
    draw(ctx) {
    ctx.strokeStyle = hsl(hue);
    ctx.lineWidth = radius / 3;
    ctx.beginPath();
    ctx.arc(this.#x, this.#y, radius + 1, 0, 2 * Math.PI);
    ctx.stroke();
    }
    }

    const myCursor = new Cursor();

    const canvas = new Canvas({
    // Append the canvas onto the <div id="canvas"> element
    parent: document.getElementById('canvas'),
    // Set the background and border colors on focus/unfocus
    background: '#aabbcc',
    border: 'black',
    borderBlur: 'gray',
    showMouse: false,
    numLayers: 2,
    // The following 2 functions are event listeners for mouse/keyboard input (when the canvas is selected)
    mousemove(x, y) {
    // Change paint color depending on which mouse button was pressed
    // We could use the `mousedown(button)` event listener to record which
    // mouse button is down in a global `mouseButtonDown` variable, instead
    // of calling `isMouseButtonDown(button)` each time the mouse moves.
    if (canvas.isMouseButtonDown(LCLICK)) {
    canvas.draw(new Circle(hsl(hue), x, y)); // Draws on layer 0 automatically
    } else if (canvas.isMouseButtonDown(MCLICK)) {
    canvas.draw(new Circle(hsl(hue + 90), x, y));
    } else if (canvas.isMouseButtonDown(RCLICK)) {
    canvas.draw(new Circle(hsl(hue + 180), x, y));
    }
    canvas.clear(1);
    myCursor.updatePos(x, y);
    canvas.draw(myCursor, 1); // Draw on layer 1
    },
    keydown(key) {
    switch (key) {
    case ('arrowup'): {
    // Increase the brush size
    if (radius < 10) {
    radius++;
    }
    break;
    }
    case ('arrowdown'): {
    // Decrease the brush size
    if (radius > 1) {
    radius--;
    }
    break;
    }
    case ('arrowleft'): {
    // Change color by -45deg
    hue -= 45;
    break;
    }
    case ('arrowright'): {
    // Change color by 45deg
    hue += 45;
    break;
    }
    case ('p'): {
    // Take and save a screenshot
    canvas.screenshot();
    break;
    }
    case ('o'): {
    // Capture a screen recording
    if (canvas.isRecording()) {
    canvas.stopRecording();
    } else {
    canvas.startRecording();
    }
    break;
    }
    }
    canvas.clear(1);
    canvas.draw(myCursor, 1); // Draw on layer 1
    }
    });

    In the base project directory Simple_Paint_demo/, run the following command. If changes are made in the source files, the demo will automatically be recompiled. Press CTRL+C to stop running.

    npx webpack --mode development --watch
    

    Open the index.html file in any web browser of your choice! You should see a page similar to the one below.