react-annotation-konvas
v0.0.6
Published
Reusable annotation library built on React Konva
Maintainers
Readme
react-annotation-konvas
A reusable React canvas annotation component built on top of React Konva. Supports drawing rectangles, ellipses, and polygons on an image, with drag-to-move, resize, zoom, pan, and hide/show controls.
Installation
npm install react-annotation-konvas konva react-konva use-image
konva,react-konva, anduse-imageare peer dependencies and must be installed alongside this package.
Quick Start
import { useRef } from "react";
import { LabelKonvas } from "react-annotation-konvas";
import type {
LabelKonvasHandle,
AnnotationData,
} from "react-annotation-konvas";
function App() {
const konvasRef = useRef<LabelKonvasHandle>(null);
const handleDataChange = (data: AnnotationData) => {
console.log("Annotations:", data.annotations);
};
return (
<LabelKonvas
ref={konvasRef}
imagePath="/images/photo.jpg"
width={900}
height={600}
rectMode={true}
selectedLabel="Cat"
onDataChange={handleDataChange}
/>
);
}Props
| Prop | Type | Default | Description |
| -------------------- | -------------------------------- | --------- | ----------------------------------------------------------------- |
| imagePath | string | — | URL or path string to the image (e.g. "/images/photo.jpg") |
| width | string \| number | "100%" | Width of the canvas container |
| height | string \| number | "100%" | Height of the canvas container |
| rectMode | boolean | false | Enable rectangle drawing mode |
| ellipseMode | boolean | false | Enable ellipse drawing mode |
| polygonMode | boolean | false | Enable polygon drawing mode |
| panMode | boolean | false | Enable pan/drag mode |
| selectedLabel | string | — | Label name assigned to newly drawn annotations |
| zoom | number | 1 | Controlled zoom level (1 = 100%) |
| initialAnnotations | Annotation[] | [] | Pre-load existing annotations onto the canvas |
| initialHiddenIds | number[] | [] | IDs of annotations to hide on initial render |
| selectedStyle | ShapeStyle | see below | Style applied to the currently selected shape |
| unselectedStyle | ShapeStyle | see below | Style applied to unselected shapes |
| drawingStyle | ShapeStyle | see below | Style applied to the shape being actively drawn |
| onDataChange | (data: AnnotationData) => void | — | Fires whenever annotations change |
| onModeChange | (key, active) => void | — | Fires when a drawing mode auto-deactivates after shape completion |
| onSelectedIdChange | (id: number \| null) => void | — | Fires when the selected annotation changes |
| onHiddenIdsChange | (ids: number[]) => void | — | Fires when the hidden annotation list changes |
| onZoomChange | (zoom: number) => void | — | Fires when zoom changes internally (e.g. scroll) |
ShapeStyle
type ShapeStyle = {
stroke?: string; // border color, e.g. "#FF4D4F"
fill?: string; // fill color, e.g. "rgba(255,77,79,0.25)"
strokeWidth?: number; // border thickness in px
dash?: number[]; // dash pattern, e.g. [4, 4]
pointsColor?: string; // polygon vertex dot color
};Default styles:
// Selected shape
selectedStyle = {
stroke: "#1677ff",
fill: "rgba(22,119,255,0.2)",
strokeWidth: 2,
};
// Unselected shapes
unselectedStyle = {
stroke: "#FF4D4F",
fill: "rgba(255,77,79,0.25)",
strokeWidth: 2,
};
// Shape being drawn
drawingStyle = {
stroke: "#FF4D4F",
fill: "rgba(255,77,79,0.25)",
strokeWidth: 2,
dash: [4, 4],
};Imperative API (ref)
Use a ref to call methods on the component imperatively:
const konvasRef = useRef<LabelKonvasHandle>(null);
// Delete the currently selected annotation
konvasRef.current?.deleteSelected();
// Delete a specific annotation by id
konvasRef.current?.deleteAnnotation(42);
// Delete all annotations
konvasRef.current?.deleteAll();
// Show/hide a specific annotation
konvasRef.current?.toggleHide(42);
// Programmatically select an annotation
konvasRef.current?.setSelectedId(42);
// Set the label for the next drawn annotation
konvasRef.current?.setSelectedLabel("Dog");
// Zoom controls
konvasRef.current?.zoomIn();
konvasRef.current?.zoomOut();
konvasRef.current?.zoomToFit();
// Get/Set annotations (useful for multi-image workflows)
const annotations = konvasRef.current?.getAnnotations();
konvasRef.current?.setAnnotations(savedAnnotations);| Method | Description |
| ----------------------------- | ----------------------------------------------------------- |
| deleteSelected() | Deletes the currently selected annotation |
| deleteAll() | Deletes all annotations |
| deleteAnnotation(id) | Deletes a specific annotation by ID |
| toggleHide(id) | Shows/hides a specific annotation by ID |
| setSelectedId(id) | Programmatically selects an annotation (null to deselect) |
| setSelectedLabel(label) | Sets the label for the next drawn annotation |
| getAnnotations() | Returns all current annotations as Annotation[] |
| setAnnotations(annotations) | Loads an array of annotations onto the canvas |
| zoomIn() | Zooms in by 20% |
| zoomOut() | Zooms out by 20% |
| zoomToFit() | Resets zoom to 1 and centers the canvas |
Data Types
type AnnotationData = {
image: string; // the imagePath value
annotations: Annotation[];
};
type Annotation = RectAnnotation | EllipseAnnotation | PolygonAnnotation;
type RectAnnotation = {
id: number;
label: string;
type: "rect";
color?: string; // auto-detected dominant color from image region
x: number; // left edge in image pixels
y: number; // top edge in image pixels
width: number;
height: number;
};
type EllipseAnnotation = {
id: number;
label: string;
type: "ellipse";
color?: string;
x: number; // center x in image pixels
y: number; // center y in image pixels
radiusX: number;
radiusY: number;
};
type PolygonAnnotation = {
id: number;
label: string;
type: "polygon";
color?: string;
points: number[]; // flat array [x1, y1, x2, y2, ...] in image pixels
};LabelSidebar Component (Example)
Below is an example sidebar component that displays label options and annotation regions with hide/show/delete controls. This component is not exported from the package — copy and customize it for your own project.
// Example usage with your own LabelSidebar component
import LabelSidebar from "./components/LabelSidebar";
<LabelSidebar
labels={["Cat", "Dog", "Car"]}
selectedLabel={selectedLabel}
onSelect={(label) => setSelectedLabel(label)}
annotations={annotationData?.annotations ?? []}
selectedId={selectedId}
hiddenIds={hiddenIds}
onSelectAnnotation={(id) => konvasRef.current?.setSelectedId(id)}
onDeleteAnnotation={(id) => konvasRef.current?.deleteAnnotation(id)}
onToggleHide={(id) => konvasRef.current?.toggleHide(id)}
/>;LabelSidebar Props
| Prop | Type | Default | Description |
| -------------------- | ------------------------- | ------- | ----------------------------------------------------- |
| labels | string[] | — | List of label names to display in the sidebar |
| selectedLabel | string | — | Currently selected label name |
| onSelect | (label: string) => void | — | Callback when a label is clicked |
| annotations | Annotation[] | [] | List of annotations to display in the regions section |
| selectedId | number \| null | — | ID of the currently selected annotation |
| hiddenIds | number[] | [] | IDs of annotations that are hidden |
| onSelectAnnotation | (id: number) => void | — | Callback when an annotation row is clicked |
| onDeleteAnnotation | (id: number) => void | — | Callback when the delete button is clicked |
| onToggleHide | (id: number) => void | — | Callback when the visibility toggle button is clicked |
The sidebar has two sections:
- Labels section (top) — Shows clickable label options for annotating
- Regions section (bottom) — Shows drawn annotations with shape icon, color swatch, label name, hide/show toggle, and delete button
The divider between sections is draggable to resize.
import React, { useRef, useState, useCallback } from "react";
import CropSquareIcon from "@mui/icons-material/CropSquare";
import CircleOutlinedIcon from "@mui/icons-material/CircleOutlined";
import HexagonOutlinedIcon from "@mui/icons-material/HexagonOutlined";
import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined";
import type { Annotation } from "react-annotation-konvas";
type LabelSidebarProps = {
labels: string[];
selectedLabel?: string;
onSelect?: (label: string) => void;
annotations?: Annotation[];
selectedId?: number | null;
hiddenIds?: number[];
onSelectAnnotation?: (id: number) => void;
onDeleteAnnotation?: (id: number) => void;
onToggleHide?: (id: number) => void;
};
const ShapeIcon = ({ type }: { type: Annotation["type"] }) => {
const style = { fontSize: "15px" };
if (type === "rect") return <CropSquareIcon style={style} />;
if (type === "ellipse") return <CircleOutlinedIcon style={style} />;
return <HexagonOutlinedIcon style={style} />;
};
const iconBtn: React.CSSProperties = {
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "22px",
height: "22px",
borderRadius: "4px",
border: "none",
background: "transparent",
cursor: "pointer",
padding: 0,
color: "#666",
flexShrink: 0,
};
const LabelSidebar = ({
labels,
selectedLabel,
onSelect,
annotations = [],
selectedId,
hiddenIds = [],
onSelectAnnotation,
onDeleteAnnotation,
onToggleHide,
}: LabelSidebarProps) => {
const [topHeight, setTopHeight] = useState(50); // percent
const dragging = useRef(false);
const containerRef = useRef<HTMLDivElement>(null);
const onDividerMouseDown = useCallback((e: React.MouseEvent) => {
e.preventDefault();
dragging.current = true;
const onMove = (ev: MouseEvent) => {
if (!dragging.current || !containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
const pct = ((ev.clientY - rect.top) / rect.height) * 100;
setTopHeight(Math.min(85, Math.max(15, pct)));
};
const onUp = () => {
dragging.current = false;
window.removeEventListener("mousemove", onMove);
window.removeEventListener("mouseup", onUp);
};
window.addEventListener("mousemove", onMove);
window.addEventListener("mouseup", onUp);
}, []);
return (
<div
ref={containerRef}
style={{
width: "220px",
flexShrink: 0,
borderRight: "1px solid #e0e0e0",
display: "flex",
flexDirection: "column",
boxSizing: "border-box",
background: "#fafafa",
userSelect: "none",
overflow: "hidden",
}}
>
{/* ── TOP: Label list ── */}
<div
style={{
height: `${topHeight}%`,
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
>
<p
style={{
margin: "0",
padding: "14px 16px 10px",
fontSize: "11px",
fontWeight: 600,
letterSpacing: "0.08em",
textTransform: "uppercase",
color: "#888",
flexShrink: 0,
}}
>
Labels
</p>
<div style={{ overflowY: "auto", flex: 1, padding: "0 8px 8px" }}>
{labels.map((label) => (
<div
key={label}
onClick={() => onSelect?.(label)}
style={{
padding: "7px 10px",
marginBottom: "3px",
cursor: "pointer",
borderRadius: "6px",
background: selectedLabel === label ? "#1677ff" : "transparent",
color: selectedLabel === label ? "#fff" : "#1a1a1a",
fontSize: "13px",
fontWeight: selectedLabel === label ? 500 : 400,
transition: "background 0.15s, color 0.15s",
}}
onMouseEnter={(e) => {
if (selectedLabel !== label)
(e.currentTarget as HTMLDivElement).style.background =
"#efefef";
}}
onMouseLeave={(e) => {
if (selectedLabel !== label)
(e.currentTarget as HTMLDivElement).style.background =
"transparent";
}}
>
{label}
</div>
))}
</div>
</div>
{/* ── DIVIDER ── */}
<div
onMouseDown={onDividerMouseDown}
style={{
height: "6px",
background: "#e0e0e0",
cursor: "row-resize",
flexShrink: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
width: "32px",
height: "2px",
borderRadius: "2px",
background: "#bbb",
}}
/>
</div>
{/* ── BOTTOM: Drawn regions ── */}
<div
style={{
flex: 1,
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}
>
<p
style={{
margin: "0",
padding: "10px 16px 8px",
fontSize: "11px",
fontWeight: 600,
letterSpacing: "0.08em",
textTransform: "uppercase",
color: "#888",
flexShrink: 0,
}}
>
Regions ({annotations.length})
</p>
<div style={{ overflowY: "auto", flex: 1, padding: "0 8px 8px" }}>
{annotations.length === 0 && (
<p
style={{
fontSize: "12px",
color: "#aaa",
padding: "4px 6px",
margin: 0,
}}
>
No regions drawn yet.
</p>
)}
{annotations.map((ann) => {
const isSelected = selectedId === ann.id;
const isHidden = hiddenIds.includes(ann.id);
return (
<div
key={ann.id}
onClick={() => onSelectAnnotation?.(ann.id)}
style={{
display: "flex",
alignItems: "center",
gap: "6px",
padding: "5px 8px",
marginBottom: "3px",
borderRadius: "6px",
cursor: "pointer",
background: isSelected ? "#e6f0ff" : "transparent",
border: isSelected
? "1px solid #1677ff"
: "1px solid transparent",
opacity: isHidden ? 0.4 : 1,
transition: "background 0.15s",
}}
onMouseEnter={(e) => {
if (!isSelected)
(e.currentTarget as HTMLDivElement).style.background =
"#efefef";
}}
onMouseLeave={(e) => {
if (!isSelected)
(e.currentTarget as HTMLDivElement).style.background =
"transparent";
}}
>
{/* Shape icon button */}
<div
style={{
...iconBtn,
color: isSelected ? "#1677ff" : "#555",
flexShrink: 0,
}}
>
<ShapeIcon type={ann.type} />
</div>
{/* Summarized color swatch */}
<div
title={ann.color ?? "no color"}
style={{
width: "14px",
height: "14px",
borderRadius: "3px",
background: ann.color ?? "#ccc",
border: "1px solid rgba(0,0,0,0.15)",
flexShrink: 0,
}}
/>
{/* Label name */}
<span
style={{
flex: 1,
fontSize: "12px",
fontWeight: 500,
color: "#1a1a1a",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{ann.label || (
<span style={{ color: "#aaa", fontStyle: "italic" }}>
unlabeled
</span>
)}
</span>
{/* Action icons */}
<button
style={iconBtn}
title={isHidden ? "Show" : "Hide"}
onClick={(e) => {
e.stopPropagation();
onToggleHide?.(ann.id);
}}
>
{isHidden ? (
<VisibilityOffOutlinedIcon style={{ fontSize: "14px" }} />
) : (
<VisibilityOutlinedIcon style={{ fontSize: "14px" }} />
)}
</button>
<button
style={{ ...iconBtn, color: "#d32f2f" }}
title="Delete"
onClick={(e) => {
e.stopPropagation();
onDeleteAnnotation?.(ann.id);
}}
>
<DeleteOutlineOutlinedIcon style={{ fontSize: "14px" }} />
</button>
</div>
);
})}
</div>
</div>
</div>
);
};
export default LabelSidebar;ToolButton Component (Example)
Below is an example toolbar button component with active state. This component is not exported from the package — copy and customize it for your own project.
// Example usage with your own ToolButton component
import ToolButton from "./components/ToolButton";
<ToolButton
onClick={() => setIsRectMode((p) => !p)}
active={isRectMode}
title="Rectangle Tool"
>
<RectIcon />
</ToolButton>;ToolButton Props
| Prop | Type | Default | Description |
| ----------- | --------------------- | ------- | --------------------------------------------- |
| onClick | () => void | — | Click handler |
| active | boolean | false | Whether the button is in active/pressed state |
| title | string | — | Tooltip text |
| children | React.ReactNode | — | Button content (typically an icon) |
| className | string | — | Additional CSS class |
| style | React.CSSProperties | — | Inline style overrides |
import React from "react";
type ToolButtonProps = {
onClick: () => void;
active?: boolean;
title?: string;
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
};
const ToolButton = ({
onClick,
active = false,
title,
children,
className,
style,
}: ToolButtonProps) => {
return (
<button
onClick={onClick}
title={title}
className={className}
style={{
background: active ? "#fd8789" : "#ffffff",
border: "1px solid #ccc",
height: "30px",
width: "30px",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: active ? "#fff" : "#555",
transition: "all 0.2s ease",
...style,
}}
>
{children}
</button>
);
};
export default ToolButton;Full Example with Toolbar, Sidebar, and Multi-Image Navigation
import { useState, useRef, useEffect } from "react";
import { LabelKonvas } from "react-annotation-konvas";
import type {
LabelKonvasHandle,
AnnotationData,
Annotation,
} from "react-annotation-konvas";
// Import your own custom components (see example implementations above)
import LabelSidebar from "./components/LabelSidebar";
import ToolButton from "./components/ToolButton";
const labels = ["Cat", "Dog", "Car", "Person"];
const images = [
"/images/photo1.jpg",
"/images/photo2.jpg",
"/images/photo3.jpg",
];
function AnnotationTool() {
const konvasRef = useRef<LabelKonvasHandle>(null);
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [annotationDataList, setAnnotationDataList] = useState<
(AnnotationData | null)[]
>(images.map(() => null));
const [annotationData, setAnnotationData] = useState<AnnotationData | null>(
null,
);
const [isRectMode, setIsRectMode] = useState(false);
const [isEllipseMode, setIsEllipseMode] = useState(false);
const [isPolygonMode, setIsPolygonMode] = useState(false);
const [isPanMode, setIsPanMode] = useState(false);
const [selectedLabel, setSelectedLabel] = useState<string | undefined>();
const [selectedId, setSelectedId] = useState<number | null>(null);
const [hiddenIds, setHiddenIds] = useState<number[]>([]);
const [zoom, setZoom] = useState(1);
// Save current annotations before switching images
const saveCurrentAnnotations = () => {
const annotations = konvasRef.current?.getAnnotations() ?? [];
setAnnotationDataList((prev) => {
const updated = [...prev];
updated[currentImageIndex] = {
id: currentImageIndex,
image: images[currentImageIndex],
annotations,
};
return updated;
});
};
const handlePrevImage = () => {
saveCurrentAnnotations();
setCurrentImageIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1));
};
const handleNextImage = () => {
saveCurrentAnnotations();
setCurrentImageIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
};
// Restore annotations when switching images
useEffect(() => {
const savedData = annotationDataList[currentImageIndex];
if (savedData?.annotations && savedData.annotations.length > 0) {
konvasRef.current?.setAnnotations(savedData.annotations);
} else {
konvasRef.current?.deleteAll();
}
}, [currentImageIndex]);
return (
<div style={{ display: "flex", height: "100vh" }}>
{/* Sidebar */}
<LabelSidebar
labels={labels}
selectedLabel={selectedLabel}
onSelect={(label) => {
setSelectedLabel(label);
konvasRef.current?.setSelectedLabel(label);
}}
annotations={annotationData?.annotations ?? []}
selectedId={selectedId}
hiddenIds={hiddenIds}
onSelectAnnotation={(id) => konvasRef.current?.setSelectedId(id)}
onDeleteAnnotation={(id) => konvasRef.current?.deleteAnnotation(id)}
onToggleHide={(id) => konvasRef.current?.toggleHide(id)}
/>
{/* Main area */}
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
{/* Toolbar */}
<div
style={{
display: "flex",
gap: 8,
padding: 8,
borderBottom: "1px solid #ccc",
alignItems: "center",
}}
>
<ToolButton
onClick={() => {
setIsRectMode((p) => !p);
setIsEllipseMode(false);
setIsPolygonMode(false);
setIsPanMode(false);
}}
active={isRectMode}
title="Rectangle"
>
▢
</ToolButton>
<ToolButton
onClick={() => {
setIsEllipseMode((p) => !p);
setIsRectMode(false);
setIsPolygonMode(false);
setIsPanMode(false);
}}
active={isEllipseMode}
title="Ellipse"
>
○
</ToolButton>
<ToolButton
onClick={() => {
setIsPolygonMode((p) => !p);
setIsRectMode(false);
setIsEllipseMode(false);
setIsPanMode(false);
}}
active={isPolygonMode}
title="Polygon"
>
⬡
</ToolButton>
<ToolButton
onClick={() => {
setIsPanMode((p) => !p);
setIsRectMode(false);
setIsEllipseMode(false);
setIsPolygonMode(false);
}}
active={isPanMode}
title="Pan"
>
✋
</ToolButton>
<ToolButton
onClick={() => konvasRef.current?.zoomIn()}
title="Zoom In"
>
+
</ToolButton>
<ToolButton
onClick={() => konvasRef.current?.zoomOut()}
title="Zoom Out"
>
-
</ToolButton>
<ToolButton
onClick={() => konvasRef.current?.zoomToFit()}
title="Fit"
>
⊡
</ToolButton>
<ToolButton
onClick={() => konvasRef.current?.deleteSelected()}
title="Delete"
>
🗑
</ToolButton>
<ToolButton
onClick={() => konvasRef.current?.deleteAll()}
title="Clear All"
>
🗑️
</ToolButton>
{/* Image Navigation */}
<div
style={{
marginLeft: "auto",
display: "flex",
alignItems: "center",
gap: 8,
}}
>
<ToolButton onClick={handlePrevImage} title="Previous Image">
←
</ToolButton>
<span style={{ fontSize: 14, fontWeight: 500 }}>
{currentImageIndex + 1} / {images.length}
</span>
<ToolButton onClick={handleNextImage} title="Next Image">
→
</ToolButton>
</div>
</div>
{/* Canvas */}
<LabelKonvas
ref={konvasRef}
imagePath={images[currentImageIndex]}
width="100%"
height="100%"
rectMode={isRectMode}
ellipseMode={isEllipseMode}
polygonMode={isPolygonMode}
panMode={isPanMode}
selectedLabel={selectedLabel}
zoom={zoom}
onZoomChange={setZoom}
onDataChange={setAnnotationData}
onSelectedIdChange={setSelectedId}
onHiddenIdsChange={setHiddenIds}
onModeChange={(key, active) => {
if (key === "rectangle") setIsRectMode(active);
if (key === "ellipse") setIsEllipseMode(active);
if (key === "polygon") setIsPolygonMode(active);
if (key === "pan") setIsPanMode(active);
}}
selectedStyle={{
stroke: "#1677ff",
fill: "rgba(22,119,255,0.2)",
strokeWidth: 2,
}}
unselectedStyle={{
stroke: "#FF4D4F",
fill: "rgba(255,77,79,0.15)",
strokeWidth: 2,
}}
drawingStyle={{
stroke: "#FF4D4F",
fill: "rgba(255,77,79,0.25)",
strokeWidth: 2,
dash: [5, 4],
}}
/>
</div>
</div>
);
}
export default AnnotationTool;Key Features for Multi-Image Support
getAnnotations()— Retrieve current annotations before switching imagessetAnnotations(annotations)— Restore saved annotations when returning to an imagedeleteAll()— Clear canvas when no saved annotations exist for an image- Annotations are stored per-image in
annotationDataListand persist while navigating
Drawing Instructions
| Shape | How to draw | | ------------- | --------------------------------------------------------------------------- | | Rectangle | Click once to set the first corner, move mouse, click again to finish | | Ellipse | Click once to set the center, move mouse, click again to finish | | Polygon | Click to add each vertex; click near the first point to close the shape | | Pan | Click and drag to pan around the canvas |
- Press Escape to cancel an in-progress rectangle or ellipse drawing.
- Click any drawn shape (while not in a drawing mode) to select it.
- Drag a selected shape to move it.
- Use the Transformer handles on selected rectangles/ellipses to resize them.
- Drag individual vertex dots on a selected polygon to reshape it.
Loading Existing Annotations
const saved: Annotation[] = [
{ id: 1, label: "Cat", type: "rect", x: 50, y: 80, width: 200, height: 150 },
{
id: 2,
label: "Dog",
type: "ellipse",
x: 300,
y: 200,
radiusX: 80,
radiusY: 50,
},
{
id: 3,
label: "Car",
type: "polygon",
points: [10, 20, 100, 20, 100, 80, 10, 80],
},
];
<LabelKonvas imagePath="/photo.jpg" initialAnnotations={saved} />;Coordinates are in image pixels (not screen pixels), so they remain accurate regardless of zoom or canvas size.
Peer Dependencies
| Package | Version |
| ------------- | -------- |
| react | ≥ 17.0.0 |
| react-dom | ≥ 17.0.0 |
| konva | ≥ 8.0.0 |
| react-konva | ≥ 18.0.0 |
| use-image | ≥ 1.0.0 |
Note: If you use the example
LabelSidebarcomponent, you'll also need@mui/icons-material,@mui/material,@emotion/react, and@emotion/styled.
License
ISC
