JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 18
  • Score
    100M100P100Q40950F
  • License MIT

Package Exports

  • @niuee/board
  • @niuee/board/index.cjs
  • @niuee/board/index.mjs

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@niuee/board) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

board

board supercharges your canvas element giving it the capabilities to pan, zoom, rotate, and much more.

continuous integration contributors contributors

InstallBare Minimum ExampleKey FeaturesHow To Use

This is not a drawing canvas library like the excalidraw or tl;draw, but a library where you can build on top of to make an app like excalidraw.

Demo The demo version is rather old and outdated but it showcases the basic functionalities so I am keeping it here. I will replace it with the newer version later on when it's stable

Documentation This documentation is still in its early stages; a lot of things are still not documented properly.

The design document contains a more detailed explanation of the underlying mechanisms of the canvas including the camera system that is essentially the backbone of this custom canvas. Board used to be called vCanvas and vCamera is the same as the BoardCamera. There was a name change for the package from vCanvas to board.

Install

If you are already bundling you project through some kind of bundler and you have npm or pnpm or other package manager to manage you dependencies. You can install the package using npm.

npm install @niuee/board

Alternatively, you can download the bundled JavaScript in the releases and put it in the same directory for other JavaScript to import.

import { Board } from "./board.js";

Or you can import from the jsdelivr cdn.

import { Board } from "https://cdn.jsdelivr.net/npm/@niuee/board@latest/index.mjs";

The above are all through esm. Additionally, the library is bundled to iife and umd as well just look for those directories. For example using the iife bundle

<script src="https://cdn.jsdelivr.net/npm/@niuee/board@latest/iife/index.js"></script>

and then in a JavaScript file you can use the exports of @niuee/board using the name board.{some export}

For example the constructor for the Board class.

const newBoard = new board.Board(canvasElement);

Bare Minimum Example

import { Board } from "@niuee/board"; // or other import style mentioned above

const canvasElement = document.querySelector("canvas");
const board = new Board(canvasElement); 

// XY axis and a reference circle at location (30, 20) would appear in debugMode
board.debugMode = true;
// get the step function of the board
const stepFn = board.getStepFunction();

// get the 2d rendering context of the canvas
const ctx = board.getContext();

// this is the callback function for the requestAnimationFrame
function step(timestamp){
    // timestamp is the argument requestAnimationFrame pass to its callback function
    
    // step the board first before everything else because stepping the board would wipe the canvas
    // pass in the timestamp as it is to the board's step function.
    stepFn(timestamp);

    // if you want to draw stuff draw it in the step function otherwise it would not persist
    // draw a circle at (100, 100) with a width of 1px
    ctx.beginPath();
    ctx.arc(100, 100, 1, 0, 2 * Math.PI);
    ctx.stroke();

    // and then call the requestAnimationFrame
    window.requestAnimationFrame(step);
}

// start the animation loop
step(0);

Key Features

  • Supports a wide variety of input methods. (touch, trackpad(macOS), keyboard mouse)
  • Works with just HTML and JavaScript but also works with React with a little bit of extra work.
  • Rich API for board camera. (You can do a lot of cool stuff with the camera)

How To Use

The Board class extends an existing canvas element in the DOM to have extra capabilities such as pan, zoom, and rotation. To instantiate a new board.

Have a canvas element in your html or create and append one to the DOM using JavaScript.

<canvas id="board"></canvas>
import { Board } from "@niuee/board";

const canvasElement = document.getElementById("board"); // or other method to get a canvas element you would like to use
const board = new Board(canvasElement); 
// if you are using this library through iife don't use the variable name board since it would have name conflict with the library

A canvas element is basically like a static image, to make it look like it's panning or something is happening to it, most libraries rely on the requestAnimationFrame function to make it move so does the @niuee/board. By default the Board class does not call the requestAnimationFrame function by itself; it relies on the user to call the requestAnimationFrame and call the Board class step function in it. It similar to how threejs would have its user call the requestAnimationFrame function in this example.

Get the step function of the Board class and call it in a requestAnimationFrame callback.

import { Board } from "@niuee/board";

const canvasElement = document.querySelector("canvas");
const board = new Board(canvasElement); 

// get the step function of the board
const stepFn = board.getStepFunction();

// this is the callback function for the requestAnimationFrame
function step(timestamp){
    // timestamp is the argument requestAnimationFrame pass to its callback function
    
    // step the board first before everything else because stepping the board would wipe the canvas
    // pass in the timestamp as it is to the board's step function.
    stepFn(timestamp);

    // do your stuff

    // and then call the requestAnimationFrame
    window.requestAnimationFrame(step);
}

// start the animation loop
step(0);

Right now, the board should have the basic functionalities. (able to pan and zoom)

But there's probably nothing on your canvas.

Enable debug mode to at least have something on the canvas.

board.debugMode = true;

You'll see an X-axis drawn in red and an Y-axis drawn in green, and a green reference circle drawn in green at location (30, 20). There is also going to have a red cross hair at the cursor's position on the canvas with the position coordinate.

The default coordinate system @niuee/board uses is the same as the one of the canvas API which is "Down" for positive Y direction.

To draw stuff on the board first get the 2d context of the board.

const ctx = board.getContext();

// draw a circle at the location (10, 10)
ctx.beginPath();
ctx.arc(10, 10, 5, 0, 2 * Math.PI);
ctx.stroke();

This would result in a circle drawn to the bottom right of the origin. The same as a regular canvas.

This is probably a good time to talk about the coordinate system @niuee/board uses.

In Most use cases the default coordinate system would be what you want as you might already be familiar with the canvas API.

In case you want to flip the Y axis so that positive direction for Y axis is point up in the screen.

Set the alignCoordinateSystem of the Board class to false.

board.alignCoordinateSystem = false;

This would flip the Y axis. There's a catch though. Even though the Y axis is flipped the context that the board uses is still the same as a regular canvas. This means that to draw something in the coordinate of the flipped coordinate system, you would need to flip the Y axis coordinate (negate the coordinate) before feeding it to the context to draw.

For example if you want to draw a circle at (30, 30), for the flipped coordinate system, you would need to do it like this.

// notice the negative sign for the y coordinate
context.arc(30, -30, 5, 0, Math.PI * 2);

There is also another difference other than the flipped Y axis. The positive direction of an angle is also reversed. For the flipped coordinate system. Positive rotation is counter clockwise. As for the default coordintate system it's the same as the regular canvas API where positive rotation is clockwise.

Camera Events

Right now there are only 3 camera events that a user can listen to which are the pan, zoom, and rotate.

To Listen to the pan event of the camera.

Because the zooming of the camera is center at the cursor point (plan to have the option to set the anchor point to the center or the top left corner of the viewport) zooming cause the camera to also pan. This will also fire the pan event.

// the pan call back 
function panCallback(event, cameraState) {
    // payload for pan event would contain the diff of the canvas pan
    // cameraState would be the current state of the abstracted camera
    console.log(event); // {diff: {x: number, y: number}}
    console.log(cameraState) // {position: Point, rotation: number, zoomLevel: number}
}

// listen to the pan event and when a pan event occur the callback would execute
board.on("pan", panCallback);

// pan event payload looks like 
// {
//     diff: Point;
// }

// cameraState looks like
// {
//     position: Point;
//     rotation: nubmer;
//     zoomLevel: number;
// }

To Listen to the zoom event.

// the rotate call back 
function rotateCallback(event, cameraState) {
    console.log(event);
    console.log(cameraState) // {position: Point, rotation: number, zoomLevel: number}
}

// listen to the rotate event and when a rotate event occur the callback would execute
board.on("rotate", rotateCallback);

// rotate event payload looks like
// {
//     deltaRotation: number;
// }

To Listen to the rotate event.

// the zoom call back 
function zoomCallback(event, cameraState) {
    console.log(event); 
    console.log(cameraState) // {position: Point, rotation: number, zoomLevel: number}
}

// listen to the zoom event and when a zoom event occur the callback would execute
board.on("zoom", zoomCallback);

// zoom event payload looks like
// {
//     deltaZoomAmount: number;
//     anchorPoint: Point;
// }

Controlling the Camera

Currently, there is no function of the Board class that directly control the underlying camera system. However, you can get the underlying BoardCamera instance using the camera property of the Board class to get the internal BoardCamera object.

Once you have the camera you can do a lot of things.

Command Conventions

Set or Move, (Set or spin for rotation operation), (Not applicable to zoom operation) Set directly set the position of the camera. It takes in the destination as argument. Move takes in the delta as an argument making it suitable for when you don't care the current position of the camera.

FromGesture or not

FromGesture is affected by the restrictions (restrictXTranslation, etc.) The design document has a detailed relationship between different kinds of camera commands.

LimitEntireViewPort or not

If limiting the entire view port the entire view port would be contrained within the boundaries not just the center of the view port (or the camera position). I would post a gif demonstrating the difference.

Clamp or not

If there is no clamping then the command would not take effect if the destination is out of bounds. There is a graph in the design document.

For example you can call the moveWithClampFromGesture(delta: {x: number, y: number}) to "move" the camera to a position clamped inside the boundaries from gesture so that the restrictions imposed would limit this command.

The documentation has all the apis listed.

Camera Attributes

restrict-{x-translation | y-translation | rotation | zoom}

This is to restrict any kind of input from the gestures (mouse-keyboard input, trackpad gesture, touch points) to move, rotate, or zoom the canvas.
For Example, to limit the absolute x direction translation set the attribute restrict-x-translation on the html tag.

This will restrict the ability to pan the canvas in x-direction.

board.restrictXTranslation = true;

restrict-relative-{x-translation | y-translation}

This is to restrict any kind of input from the gestures (mouse-keyboard input, trackpad gesture, touch points) to move relative to the camera viewport. X is the left and right direction of the view port and Y is the up and down direction.

board.restrictRelativeYTranslation = true;

full-screen

This is to set the dimensions of the canvas to be the same as window.innerHeight and window.innerWidth.
This will override the width and height attribute.

board.fullScreen = true;

width

This will change the width of the canvas element. The Board class instance also listens to the attribute change of the underlying canvas element. So if you can also change the width of the canvas element; the board class instance will take care of the change under the hood.

board.width = 300;

OR

<canvas width="300"></canvas>

height

This is to set the height of the canvas. This is similar to the width attribute. The board also listens to the height change of the canvas element it controls; you can change the height using JavasScript or directly in html.

board.height = 300;

OR

<canvas height="300"></canvas>

control-step

This is to prevent the canvas from calling the window.requestAnimationFrame automatically. Default is "true"(meaning that the canvas element would not call rAF itself the user would have to "control the step function"; I know it's kind of confusing I am still working on the name though)

Setting this attribute to false the canvas would handle the calling of rAF and the user would just get the pan, zoom, and rotate functionality automatically. However, in this mode you would probably have to go into the source code of the canvas and add stuff to the step function to actually acheive anything.

board.stepControl = false;

debug-mode

This would switch on the debug mode for the canvas. Currently, the debug mode is drawing the reference circle in green, the axis in their respective color, the bounding box in blue. The cursor icon would be replaced with a red crosshair and at the top right to the crosshair would be the position of the cursor in world coordinate.

The Board way.

board.debugMode = true;

max-half-trans-width

This is to set the horizontal boundaries for the viewport. (where the camera can move to) Currently, the boundaries are set mirrored at the origin. Hence the "half" in the attribute name. Left and right both gets the same value. The entire horizontal boundary is then 2 * half width wide.

board.maxHalfTransWidth = 1000;

max-half-trans-height

This is to set the vertical boundaries for the viewport. Currently, the boundaries are set mirrored at the origin. Hence the "half" in the attribute name. Top and bottom both gets the same value. The entire vertical boundary is then 2 * half width wide.

The Board way.

board.maxHalfTransHeight = 1000;

grid

This is to toggle the grid displayed on the canvas. The spacing currently is not adjustable; it is the same as the ruler (it flexible depending on the zoom).

board.displayGrid = true;

ruler

This is to toggle the ruler displayed on the canvas. The spacing depends on the zoom level.

board.displayRuler = true;