
Security News
Feross on TBPN: How North Korea Hijacked Axios
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.
@practicaljs/react-canvas-kit
Advanced tools
React canvas component, with basic functionality as zoom, pan, resize and a portal for rendering popups using dom elements. This library is meant to be used with the Canvas API
We're excited to announce that we are in the process of developing new documentation to cover the extensive features of this library and the Canvas-Kit APIs. This new documentation, currently in beta, aims to provide comprehensive guides, examples, and API references to enhance your development experience.
If you're eager to explore these new resources and start integrating them into your projects, check out the RCK Documentation.
We value your feedback as we strive to improve and finalize our documentation. If you have any suggestions, encounter any issues, or have questions, please don't hesitate to reach out via [insert feedback channel here - could be an email, GitHub link, etc.].
Thank you for your support and contributions to making our documentation better for everyone!
A comprehensive toolkit for handling common canvas functions in React. It includes features like setting up the canvas, managing transformations, handling events, and rendering popovers (coming... ).
First install the react canvas kit
npm i @practicaljs/react-canvas-kit
This command installs the required peer dependencies. If not run
npm i @practicaljs/canvas-kit @practicaljs/priority-queue
import { CanvasContainer } from "@practicaljs/react-canvas-kit";
const CanvasManager = () => <div style={{ width: "100%" }}></div>;
function App() {
return (
<>
<CanvasContainer>
<CanvasManager />
</CanvasContainer>
</>
);
}
The Canvas Container provides a singleton transform instance and event listeners for canvas interactions.
Your manager component is where you'll be placing all the events listeners and your floating action buttons
/** new line **/
import {
CanvasContainer,
getCanvas2DContext,
} from "@practicaljs/react-canvas-kit";
import { getCanvasPoint } from "@practicaljs/canvas-kit";
const drawRectangle = (
x: number,
y: number,
ctx: CanvasRenderingContext2D,
path?: CanvasPath2D,
) => {
if (!path) {
path = new Path2D();
path.roundRect(x, y, 100, 100, 4);
}
ctx.restore();
ctx.beginPath();
ctx.strokeStyle = "#646cff";
ctx.lineWidth = 4;
ctx.stroke(path);
return path;
};
const handleClick = (e: React.MouseEvent) => {
const ctx: CanvasRenderingContext2D | null = getCanvas2DContext();
if (!ctx) return;
const [x, y] = getCanvasPoint(
e.nativeEvent.offsetX,
e.nativeEvent.offsetY,
ctx,
false,
);
drawRectangle(x, y, ctx);
};
const CanvasManager = () => (
<div style={{ width: "100%" }} onClick={handleClick}></div>
);
drawRectangle method to store paths:const paths: Path2D[] = [];
const drawRectangle = (
x: number,
y: number,
ctx: CanvasRenderingContext2D,
path?: Path2D,
) => {
if (!path) {
path = new Path2D();
path.roundRect(x, y, 100, 100, 4);
// add your new path to paths
paths.push(path);
}
ctx.restore();
ctx.beginPath();
ctx.strokeStyle = "#646cff";
ctx.lineWidth = 4;
ctx.stroke(path);
return path;
};
const redraw = () => {
const ctx = getCanvas2DContext();
if (!ctx) return;
// loop througth every path added and call your redraw rectangle and pass in that component
// x and y are ignored since the path already has those values
for (let path of paths) {
window.requestAnimationFrame(() => drawRectangle(0, 0, ctx, path));
}
};
const CanvasManager = () => {
useRedrawEvent(redraw, []);
return <div style={{ width: "100%" }}></div>;
};
Try scrolling and zooming using
Built in scroll: Mouse wheel and trackpad
Built in zoom: ctrl/⌘ +, ctrl/⌘ -, ctrl/⌘ mouse wheel
To show how to start using the canvasTransform lets create a custom zoom
component. In react we'll listen to it's changes with
useSyncExternalStore(canvasTransform.subscribe, canvasTransform.getSnapshot)
By the way you can also subsribe your own listener outside of react by just calling the subscribe method directly
import {
canvasTransform,
getCanvas2DContext,
requestRedraw,
} from "@practicaljs/react-canvas-kit";
import { useSyncExternalStore } from "react";
const handleZoomOut = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const ctx = getCanvas2DContext();
if (!ctx) return;
canvasTransform.changeScale(-0.1, ctx);
requestRedraw();
};
const handleZoomIn = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const ctx = getCanvas2DContext();
if (!ctx) return;
canvasTransform.changeScale(0.1, ctx);
requestRedraw();
};
const resetZoom = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const ctx = getCanvas2DContext();
if (!ctx) return;
const change = 1 - canvasTransform.scale;
canvasTransform.changeScale(change, ctx);
requestRedraw();
};
export const ZoomComponent = () => {
const { scale } = useSyncExternalStore(
canvasTransform.subscribe,
canvasTransform.getSnapshot,
);
return (
<>
<button onClick={handleZoomOut}>-</button>
<button onClick={resetZoom}>{Math.round(scale * 100)}%</button>
<button onClick={handleZoomIn}>+</button>
</>
);
};
export const CanvasManager = () => {
useRedrawEvent(redraw, []);
return (
<div style={{ width: "100%" }} onClick={handleClick}>
<ZoomComponent />
</div>
);
};
Test it out
One of the most common things you will perform is to recenter or scroll to a shape, later on we'll also do recenter on content
One is to recenter in the middle of the canvas.
The other will scroll the canvas to a point ( in this case I'm using the first shape we've created )
Note: This method does not reset the scale (zoom), you can do that separately as in the ZoomComponent
const recenter = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const ctx = getCanvas2DContext();
if (!ctx) return;
canvasTransform.recenter(ctx);
requestRedraw();
};
const recenterOnShape = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const ctx = getCanvas2DContext();
if (!ctx || !paths.length) return;
const firstPath = paths[0];
canvasTransform.recenter(
ctx,
firstPath.trackingPoint.x,
firstPath.trackingPoint.y,
);
requestRedraw();
};
export const CanvasManager = () => {
useRedrawEvent(redraw, []);
return (
<div
style={{ width: "100%" }}
onClick={handleClick}
onMouseMove={checkIfInNode}
>
<ZoomComponent />
<button onClick={recenter}>Recenter</button>
<button onClick={recenterOnShape}>Recenter Shape</button>
</div>
);
};
One popular action is to recenter the canvas around content and also scale to fit, for this we'll need to track the shapes created.
import { CanvasPath2D } from "@practicaljs/react-canvas-kit";
const paths: CanvasPath2D[] = [];
const drawRectangle = (
x: number,
y: number,
ctx: CanvasRenderingContext2D,
path?: CanvasPath2D,
) => {
if (!path) {
const key = crypto.randomUUID();
path = new CanvasPath2D({
key,
trackingPoint: { x: x + 50, y: y + 50 },
});
path.roundRect(x, y, 100, 100, 4);
paths.push(path);
}
ctx.restore();
ctx.beginPath();
ctx.strokeStyle = "#646cff";
ctx.lineWidth = 4;
ctx.stroke(path);
return path;
};
For the tracking point you can choose x or y, but I want to treat the center of my rectangle as the tracking point.
canvasTransform.trackShapeconst handleClick = (e: React.MouseEvent) => {
const ctx = getCanvas2DContext();
if (!ctx) return;
const [x, y] = getCanvasPoint(
e.nativeEvent.offsetX,
e.nativeEvent.offsetY,
ctx,
false,
);
const path = drawRectangle(x, y, ctx);
canvasTransform.trackShape(
path.key,
path.trackingPoint.x,
path.trackingPoint.y,
);
};
const recenterOnContent = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const ctx = getCanvas2DContext();
if (!ctx) return;
canvasTransform.recenterOnContent(ctx, false);
requestRedraw();
};
const scaleToFit = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const ctx = getCanvas2DContext();
if (!ctx) return;
canvasTransform.recenterOnContent(ctx, true);
requestRedraw();
};
export const CanvasManager = () => {
useRedrawEvent(redraw, []);
return (
<div
style={{ width: "100%" }}
onClick={handleClick}
onMouseMove={checkIfInNode}
>
<ZoomComponent />
<button onClick={recenter}>Recenter</button>
<button onClick={recenterOnShape}>Recenter Shape</button>
<button onClick={recenterOnContent}>Recenter around content</button>
<button onClick={scaleToFit}>Recenter around content and scale</button>
</div>
);
};
The <CanvasFab {...props} /> element allows for regular dom elements to be
placed on top of canvas components like toolbars.
const CanvasManager = (props) => {
return (
<>
<div
style={{ width: "100%" }}
onClick={handleClick}
>
{/*rest of code */}
</div>
<CanvasFab
fabId="fab-id"
offsetTop={50}
orientation="horizontal"
placement="top"
>
<ButtonGroup>
<Button onClick={() => console.log("Option 1")}>Option 1</Button>
<Button>Option 2</Button>
</ButtonGroup>
</CanvasFab>
</>
);
};
The fabId to allow for multiple types of fabs, but only one can be active at a time.
const checkIfInNode = (e: React.MouseEvent) => {
const ctx = getCanvas2DContext();
if (!ctx) return null;
const clientX = e.nativeEvent.offsetX;
const clientY = e.nativeEvent.offsetY;
const [x, y] = getCanvasPoint(clientX, clientY, ctx, true);
for (const path of paths) {
if (ctx.isPointInPath(path.path, x, y)) {
return path;
}
}
return null;
};
const onClick = (e: React.MouseEvent) => {
const ctx = getCanvas2DContext();
if (!ctx) return;
const clickedPath = checkIfInNode(e);
const modal = getFabContext("fab-id");
modal.openFab({
// the y coordinate has a 10 padding ( is minus because we are rendering on the top )
position: { x: clickedPath.trackingPoint.x, y: clickedPath.point.y - 10 },
key: clickedPath.key,
path: clickedPath.path,
});
return;
};
Like any other method in this library, we opted to use services to allow for use outside of react.
// for horizontal and top
modal.openFab({
position: { x: clickedPath.trackingPoint.x, y: clickedPath.point.y - 10 },
...rest,
});
// horizontal and bottom
modal.openFab({
position: {
x: clickedPath.trackingPoint.x,
y: clickedPath.point.y + clickedPath.width + 10,
},
...rest,
});
// vertical and left
modal.openFab({
position: { x: clickedPath.point.x - 10, y: clickedPath.trackingPoint.y },
...rest,
});
// vertical and right
modal.openFab({
position: {
x: clickedPath.point.x + clickedPath.width + 10,
y: clickedPath.trackingPoint.y,
},
...rest,
});
const fab = getFabContext("fab-id");
if (fab.open && fab.key === path.key) {
// make sure the fab point matches the open fab point logic
const fabPoint = {
x: path.trackingPoint.x,
y: path.point.y + path.width + 10,
};
fab.changeFabPosition(fabPoint);
}
const context = getFabContext("one-and-only");
context.close();
We have a click outside listener that will automatically close when clicking outside the element or the FAB container
You've successfully set up and explored the Canvas React Kit. Feel free to explore further and customize the Canvas React Kit for your project needs. If you have questions, you can always reach out to me on twitter @AlvarezHarlen. Happy coding!
I'll be adding the popover section to give you an easy to use api to render popover dom elements
FAQs
React canvas component, with basic functionality as zoom, pan, resize and a portal for rendering popups using dom elements. This library is meant to be used with the Canvas API
The npm package @practicaljs/react-canvas-kit receives a total of 1 weekly downloads. As such, @practicaljs/react-canvas-kit popularity was classified as not popular.
We found that @practicaljs/react-canvas-kit demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.

Security News
OpenSSF has issued a high-severity advisory warning open source developers of an active Slack-based campaign using impersonation to deliver malware.

Research
/Security News
Malicious packages published to npm, PyPI, Go Modules, crates.io, and Packagist impersonate developer tooling to fetch staged malware, steal credentials and wallets, and enable remote access.