Created by: Jaymar921
A lightweight 2-Dimensional graphic utility that can be used for website or game development. Wraps all renderable images inside a CanvasScreen object.
npm install @jaymar921/2dgraphic-utils| Class | Description |
|---|---|
| CanvasScreen | A wrapper class around the <canvas> element that manages sprites and their rendering. It handles screen updates, sprite registrations, and interactions like mouse clicks and drags. |
| Sprite | Represents an image (or animation) that can be drawn onto the canvas. It has properties for position, size, animation frames, and more. |
| SpriteType | A predefined enum that categorizes different types of sprites, such as OBJECT, PLAYER, BLOCK, etc. |
| Function | Description |
|---|---|
constructor(canvasId, width, height, background) |
Initializes a CanvasScreen object, binding it to an existing canvas element in the DOM by its ID. Optional parameters allow for setting width, height, and background color. |
registerObject(spriteObj) |
Registers a Sprite object for rendering on the canvas. Order of registration matters, as later objects will overlap earlier ones. |
unregisterObject(objID) |
Removes a Sprite object from the screen by its objID. |
getRegisteredObject(objID) |
Retrieves a Sprite object by its objID. Returns null if not found. |
getAllRegisteredObjects() |
Returns a list of all registered Sprite objects currently rendered on screen. |
handleScreenClickedEvent(callbackFunc) |
Registers a callback function to be triggered when the screen is clicked or touched. |
enableScreenDrag(bool) |
Enables or disables dragging (panning) of the canvas using mouse or touch events. |
getCameraOffset() |
Returns the x and y offset coordinates of the canvas camera. Gets modified on zoom. |
getFixedCameraOffset() |
Returns the x and y offset of the canvas camera. Does not change on zoom. |
setCameraOffset(x, y) |
Update the camera position by the given x and y coordinates. |
setGlobalScale(value) |
Scale all sprites rendered inside the canvas. Also propagates the new scale to all sprite objects immediately. |
enableScreenZoom(bool) |
Enable or disable canvas zoom via mouse wheel. |
handleScreenZoomEvent(callback) |
Registers a callback function to be triggered when the screen is zoomed. |
setZoomSpeed(value) |
Set the speed of zoom. Default: 0.01 per scroll. |
isInViewport(obj, offset) |
Returns true if the given sprite is within the visible viewport. Used internally for render culling. |
static animate() |
Continuously updates the canvas by clearing the screen and redrawing all registered sprites. Runs at a capped 60 FPS. Off-screen sprites are automatically culled. |
- Coordinate space: Sprite positions (
posX,posY) are always in world space. The camera offset and global scale are applied during rendering — you never need to manually adjust sprite positions for zoom or pan. - Drag suppression: Click events are automatically suppressed after a drag so that releasing a pan does not accidentally trigger a click interaction.
- Viewport culling: Only world-space sprites that are within the visible viewport are drawn each frame. Static sprites (HUD elements) are always drawn.
| Function | Description |
|---|---|
constructor({objID, name, posX, posY, width, height, imageSource, animations, frames, frameBuffer, loop, autoPlay, scale, imageSmoothingEnabled, type}) |
Initializes a Sprite object with properties like position, size, image source, and animation settings. Note: Tested image formats are png and jpeg. Other formats may cause rendering issues. |
draw(context, offset) |
Draws the sprite on the canvas. Pass the camera offset for world-space sprites; omit it for static sprites. |
setGlobalScale(value) |
Called by CanvasScreen when the global scale changes. You do not need to call this manually. |
update(context) |
Calls draw() to render the sprite. |
play() |
Starts the animation of the sprite if it was paused. |
switchAnimation(name) |
Switches the active animation by name (must match a key in the animations object passed to the constructor). |
updateFrames() |
Updates the current frame of the sprite based on the frame buffer. Handles looping and auto-playing logic. |
- You only need to create an instance of
Spriteand register it withCanvasScreen. The screen handles all rendering automatically. - Sprite positions are world-space coordinates. The camera offset is applied by the renderer — do not offset
posX/posYmanually to compensate for panning or zoom.
| SpriteType | Description |
|---|---|
OBJECT |
Represents a general object. |
PLAYER |
Represents a player character. |
BACKGROUND |
Represents background elements in the game/scene. |
FLUID |
Represents fluid objects, such as water. |
PASSABLE |
Represents objects that can be passed through. |
ITEM |
Represents collectible items in a game. |
BLOCK |
Represents solid, non-passable objects. |
AIR |
Represents air or an empty space. |
STATIC |
Represents a static object that is not affected by camera movement or zoom. Useful for HUD elements. |
import { CanvasScreen } from "@jaymar921/2dgraphic-utils";
import { Sprite, SpriteType } from "@jaymar921/2dgraphic-utils";
const canvas = new CanvasScreen("myCanvas"); // Assumes <canvas id="myCanvas"></canvas> exists in the HTML
canvas.setCameraOffset(5, 5); // Customize the camera position | default: x=0, y=0
const playerSprite = new Sprite({
objID: "player1",
name: "Player 1",
posX: 50, // World-space X position
posY: 50, // World-space Y position
width: 32,
height: 32,
imageSource: "player-sprite.png",
type: SpriteType.PLAYER,
scale: 1,
});
canvas.registerObject(playerSprite); // Sprite is now rendered automatically
const objs = canvas.getAllRegisteredObjects(); // Returns all currently rendered spritescanvas.handleScreenClickedEvent((e) => {
const objID = e.objID; // objID of the top-most sprite that was clicked
const spriteType = e.type; // SpriteType of the top-most clicked sprite | default: SpriteType.AIR
const mouseX = e.mousePosition.x; // World-space X of the mouse cursor
const mouseY = e.mousePosition.y; // World-space Y of the mouse cursor
const layers = e.layers; // Array of all sprites at the clicked position
console.log(`Mouse clicked at world position: (${mouseX}, ${mouseY})`);
// Custom logic such as moving the player by altering posX and posY
});Note:
mousePositionis returned in world space. You can compare it directly againstsprite.posX/sprite.posYwithout any manual offset or scale correction.
canvas.enableScreenDrag(true); // Allow panning by dragging the canvas
// Pan speed is automatically adjusted for the current zoom level,
// so dragging always feels consistent regardless of how far in or out you've zoomed.// Get all sprites currently registered on the canvas
const registeredObjects = canvas.getAllRegisteredObjects(); // Array<Sprite>
// registeredObjects is passed by reference — mutating properties like posX/posY
// will be reflected on screen immediately on the next frame.
// Remove a sprite from the canvas by its objID
canvas.unregisterObject("player-1");
// Get a single registered sprite by objID
const registeredObject = canvas.getRegisteredObject("sprite-id"); // Sprite | nullIf you are working with ReactJS, here's a sample implementation using a custom hook:
useCanvas.js
// useCanvas.js
import { CanvasScreen, Sprite } from "@jaymar921/2dgraphic-utils";
import { useEffect, useState } from "react";
/**
* @param {string} canvasId
* @param {Number} width
* @param {Number} height
* @param {string} background
*/
export function useCanvas(
canvasId = "my-canvas",
width,
height,
background = "black",
) {
const [canvas, setCanvas] = useState();
useEffect(() => {
const canvas = new CanvasScreen(canvasId, width, height, background);
setCanvas(canvas);
}, [canvasId, background]);
function getFixedCameraOffset() {
return CanvasScreen.fixedCameraOffset;
}
function getCameraOffset() {
return CanvasScreen.cameraOffset;
}
function setCameraOffset(x = 0, y = 0) {
CanvasScreen.cameraOffset = { x, y };
}
function enableScreenDrag(bool) {
if (!canvas) return;
canvas.enableScreenDrag(bool);
}
function handleScreenClickedEvent(callbackFunc) {
if (!canvas) return;
canvas.handleScreenClickedEvent(callbackFunc);
}
function registerObject(sprite) {
if (!canvas) return;
canvas.registerObject(sprite);
}
function unregisterObject(objectId) {
if (!canvas) return;
canvas.unregisterObject(objectId);
}
function getRegisteredObject(objectId) {
if (!canvas) return null;
return canvas.getRegisteredObject(objectId);
}
function getAllRegisteredObjects() {
if (!canvas) return [];
return canvas.getAllRegisteredObjects();
}
function setGlobalScale(value) {
if (!canvas) return;
canvas.setGlobalScale(value);
}
function enableScreenZoom(bool) {
if (!canvas) return;
canvas.enableScreenZoom(bool);
}
function handleScreenZoomEvent(callback) {
if (!canvas) return;
canvas.handleScreenZoomEvent(callback);
}
function setZoomSpeed(value = 0.01) {
if (!canvas) return;
canvas.setZoomSpeed(value);
}
return {
registerObject,
unregisterObject,
handleScreenClickedEvent,
enableScreenDrag,
getRegisteredObject,
getAllRegisteredObjects,
getCameraOffset,
setCameraOffset,
setGlobalScale,
enableScreenZoom,
handleScreenZoomEvent,
setZoomSpeed,
getFixedCameraOffset,
};
}App.jsx
import { useEffect } from "react";
import "./App.css";
import { useCanvas } from "./hooks/useCanvas";
import { Sprite } from "@jaymar921/2dgraphic-utils";
function App() {
const canvasScreen = useCanvas("canvas-screen", 300, 300, "blue");
function handleClick(clickEvent) {
console.log(clickEvent);
}
useEffect(() => {
canvasScreen.enableScreenDrag(true);
canvasScreen.handleScreenClickedEvent(handleClick);
const spr1 = new Sprite({
objID: "spr1",
name: "sprite 1",
posX: 150,
posY: 150,
imageSource: "path-to-sprite-img",
scale: 3,
});
canvasScreen.registerObject(spr1);
}, [canvasScreen]);
return (
<>
<div className="content-center h-screen">
<canvas className="m-auto" id="canvas-screen"></canvas>
</div>
</>
);
}main.jsx
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(<App />);const player = new Sprite({
objID: "player",
name: "player 1",
posX: 150,
posY: 150,
imageSource: "path-to-idle-player-img",
scale: 3,
animations: {
walkLeft: { frames: 6, imageSource: "path-to-walk-left-img" },
walkRight: { frames: 6, imageSource: "path-to-walk-right-img" },
IdleLeft: { frames: 12, imageSource: "path-to-idle-left-img" },
IdleRight: { frames: 12, imageSource: "path-to-idle-right-img" },
},
});
// Switch animation based on game logic
if (playerIsMovingLeft) player.switchAnimation("walkLeft");
if (playerIsMovingRight) player.switchAnimation("walkRight");
if (playerIsIdleLeft) player.switchAnimation("IdleLeft");
if (playerIsIdleRight) player.switchAnimation("IdleRight");canvas.enableScreenZoom(true);
canvas.setZoomSpeed(0.05); // Faster zoom | default: 0.01
// React to zoom events
canvas.handleScreenZoomEvent(({ globalScale, event }) => {
console.log("Current zoom level:", globalScale);
});Note: Zoom is centered on the visible viewport. Panning speed and click detection are automatically corrected for the current zoom level — no manual adjustment needed.
- Fixed mouse click world-space conversion: click coordinates are now correctly transformed to world space (
offsetX / globalScale + cameraOffset.x) so hitbox detection is accurate at any zoom level. - Fixed
InHitboxto use pure world-space comparison. Previously appliedglobalScaleagain inside the hitbox check, causing misses at zoom levels other than 1. - Fixed drag-suppression for click events:
draggingstate is now exposed on theCanvasScreeninstance so the click handler can correctly suppress spurious clicks that follow a pan gesture. - Fixed
mouseuptiming: asetTimeout(..., 0)ensures theclickevent (which fires aftermouseup) still sees the dragging flag and is correctly ignored. - Fixed
lastFrameTimeinitialization to0so the first frame's FPS comparison does not produceNaN. - Fixed orphaned
context.restore()inanimate()that had no matchingcontext.save(), which would eventually cause a canvas state stack underflow. - Fixed pan delta now divided by
globalScaleso panning speed stays consistent at any zoom level. - Fixed touch coordinates now use
getBoundingClientRect()for accurate offset calculation. - Improved
setGlobalScale()now propagates the new scale to all sprite objects immediately rather than doing so inside the render loop on every frame. - Improved
animate()now iteratescanvasObjectsandstaticCanvasObjectsseparately, avoiding a new array allocation via spread on every frame. - Improved Viewport culling: world-space sprites that are entirely outside the visible viewport are skipped during rendering.
- Improved
getRegisteredObject()now uses.find()instead of.filter()[0]to avoid creating a temporary array. - Improved Zoom
globalScaleis clamped with.toFixed(4)to prevent floating-point drift at extreme zoom levels.
