A JavaScript image annotation plugin that creates Flickr-like comment annotations on images. Users can draw rectangular regions on images, add text notes, and persist annotations via callbacks or AJAX.
Works standalone (vanilla JS), or with jQuery, React, or Vue. Framework adapters are tree-shakeable — only the one you import gets bundled.
npm install annotate-imagejQuery, React, and Vue are optional peer dependencies. Install only what you use:
# For jQuery projects
npm install jquery
# For React projects
npm install react react-dom
# For Vue projects
npm install vue<link rel="stylesheet" href="dist/css/annotate.min.css">
<script src="dist/core.min.js"></script>const instance = AnnotateImage.annotate(document.getElementById('myImage'), {
editable: true,
notes: [
{ top: 286, left: 161, width: 52, height: 37,
text: 'Small people on the steps',
id: 'note-1', editable: false },
{ top: 134, left: 179, width: 68, height: 74,
text: 'National Gallery Dome',
id: 'note-2', editable: true },
],
onChange(notes) { console.log('Notes changed:', notes); },
});
// Programmatic control
instance.add(); // Enter add-note mode
instance.getNotes(); // Get current annotations
instance.clear(); // Remove all annotations
instance.destroy(); // Tear down completelyimport { annotate } from 'annotate-image';
import 'annotate-image/css';
const instance = annotate(document.getElementById('myImage'), {
editable: true,
notes: [],
});<link rel="stylesheet" href="dist/css/annotate.min.css">
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="dist/jquery.min.js"></script>$(function() {
$('#myImage').annotateImage({
editable: true,
notes: [/* ... */],
});
});
// Destroy
$('#myImage').annotateImage('destroy');Or as an ES module:
import 'annotate-image/jquery';
import 'annotate-image/css';import { useRef } from 'react';
import { AnnotateImage } from 'annotate-image/react';
import type { AnnotateImageRef } from 'annotate-image/react';
import 'annotate-image/css';
function App() {
const ref = useRef<AnnotateImageRef>(null);
return (
<>
<AnnotateImage
ref={ref}
src="/photo.jpg"
width={800}
height={600}
editable
notes={[]}
onChange={(notes) => console.log(notes)}
onSave={(note) => console.log('Saved:', note)}
onDelete={(note) => console.log('Deleted:', note)}
/>
<button onClick={() => ref.current?.add()}>Add Note</button>
<button onClick={() => console.log(ref.current?.getNotes())}>
Get Notes
</button>
</>
);
}<script setup lang="ts">
import { ref } from 'vue';
import { AnnotateImage } from 'annotate-image/vue';
import 'annotate-image/css';
const annotator = ref();
</script>
<template>
<AnnotateImage
ref="annotator"
src="/photo.jpg"
:width="800"
:height="600"
editable
:notes="[]"
@change="(notes) => console.log(notes)"
@save="(note) => console.log('Saved:', note)"
@delete="(note) => console.log('Deleted:', note)"
/>
<button @click="annotator?.add()">Add Note</button>
<button @click="console.log(annotator?.getNotes())">Get Notes</button>
</template>Creates an annotation layer on an image element. Returns an AnnotateImage instance.
| Option | Type | Default | Description |
|---|---|---|---|
editable |
boolean |
true |
Enable annotation editing |
notes |
AnnotationNote[] |
[] |
Initial annotations |
api |
AnnotateApi |
— | Server persistence (see below) |
onChange |
(notes: NoteData[]) => void |
— | Called after any notes mutation |
onSave |
(note: NoteData) => void |
— | Called after a note is saved |
onDelete |
(note: NoteData) => void |
— | Called after a note is deleted |
onLoad |
(notes: NoteData[]) => void |
— | Called after notes are loaded |
onError |
(ctx: AnnotateErrorContext) => void |
— | Called on API errors (defaults to console.error) |
Each field accepts a URL string (default fetch behavior) or a function for full control. Omit api entirely for static-only mode.
{
load?: string | (() => Promise<AnnotationNote[]>),
save?: string | ((note: NoteData) => Promise<SaveResult>),
delete?: string | ((note: NoteData) => Promise<void>),
}| Method | Returns | Description |
|---|---|---|
add() |
boolean |
Enter add-note mode. Returns false if already editing. |
clear() |
void |
Remove all annotations. |
destroy() |
void |
Tear down completely. Idempotent. |
getNotes() |
NoteData[] |
Current annotations (internal fields stripped). |
load() |
void |
Rebuild views from the current notes array. |
// Full annotation (includes internal fields)
interface AnnotationNote {
id: string;
top: number;
left: number;
width: number;
height: number;
text: string;
editable: boolean;
}
// Annotation data passed to callbacks (no internal fields)
type NoteData = Omit<AnnotationNote, 'view' | 'editable'>;| Prop | Type | Default | Description |
|---|---|---|---|
src |
string |
— | Image URL (required) |
width |
number |
— | Image width in pixels |
height |
number |
— | Image height in pixels |
notes |
AnnotationNote[] |
[] |
Initial annotations |
editable |
boolean |
true |
Enable editing |
onChange |
(notes: NoteData[]) => void |
— | Notes changed |
onSave |
(note: NoteData) => void |
— | Note saved |
onDelete |
(note: NoteData) => void |
— | Note deleted |
onLoad |
(notes: NoteData[]) => void |
— | Notes loaded |
onError |
(ctx: AnnotateErrorContext) => void |
— | Error occurred |
| Method | Returns | Description |
|---|---|---|
add() |
void |
Enter add-note mode |
clear() |
void |
Remove all annotations |
destroy() |
void |
Tear down the instance |
getNotes() |
NoteData[] |
Get current annotations |
| Prop | Type | Default | Description |
|---|---|---|---|
src |
String |
— | Image URL (required) |
width |
Number |
— | Image width in pixels |
height |
Number |
— | Image height in pixels |
notes |
AnnotationNote[] |
— | Initial annotations |
editable |
Boolean |
true |
Enable editing |
| Event | Payload | Description |
|---|---|---|
change |
NoteData[] |
Notes changed |
save |
NoteData |
Note saved |
delete |
NoteData |
Note deleted |
load |
NoteData[] |
Notes loaded |
error |
AnnotateErrorContext |
Error occurred |
| Method | Returns | Description |
|---|---|---|
add() |
void |
Enter add-note mode |
clear() |
void |
Remove all annotations |
destroy() |
void |
Tear down the instance |
getNotes() |
NoteData[] |
Get current annotations |
notes |
Ref<NoteData[]> |
Reactive current notes |
Each entry point (annotate-image, annotate-image/jquery, annotate-image/react, annotate-image/vue) is a separate bundle. Importing one does not pull in the others. Unused framework adapters are excluded automatically by bundlers that support package exports.
The onError callback receives a context object:
interface AnnotateErrorContext {
type: 'load' | 'save' | 'delete';
error: Error;
note?: AnnotationNote;
}If no onError callback is provided, errors are logged to the console.
The plugin supports keyboard navigation:
- Tab to navigate between annotation areas and controls
- Enter/Space to activate buttons and open editable annotations
- Escape to cancel editing
- Annotation areas and buttons have
role="button"andtabindexattributes - Focus is managed automatically when entering/exiting edit mode
- Visible focus styles on all interactive elements
Run the demo server locally:
npm run demoThis opens a browser at http://localhost:8080/demo/index.html with links to demos including static annotations, AJAX loading, vanilla JS, React, Vue, and multiple instances.
npm install
npm run build # Type-check, bundle, minify
npm run build:check # Type-check only
npm test # Run tests
npm run test:jquery4 # Run tests against jQuery 4dist/ contains the built artifacts:
dist/core.js— Core library (ESM, no dependencies)dist/core.min.js— Core library (IIFE, minified,AnnotateImageglobal)dist/jquery.js— jQuery adapter (ESM, jQuery external)dist/jquery.min.js— jQuery adapter (IIFE, minified, jQuery external)dist/react.js— React component (ESM, React external)dist/vue.js— Vue component (ESM, Vue external)dist/css/annotate.min.css— Minified stylesdist/types/— TypeScript declaration files
All modern browsers (Chrome, Firefox, Safari, Edge). The plugin uses pointer events and fetch, so IE is not supported.
- Package renamed from
jquery-image-annotatetoannotate-image - Rewritten in TypeScript with vanilla DOM internals
- Removed jQuery UI dependency — drag/resize uses vanilla pointer events
- Added core vanilla JS API (
annotate()factory) that works without jQuery - jQuery adapter is now a thin wrapper registering
$.fn.annotateImage - Added React 18+ and Vue 3+ framework components
- Lifecycle callbacks:
onChange,onSave,onDelete,onLoad getNotes()method for reading current annotations- Replaced
$.ajax/$.getJSONwithfetch - Build system replaced: Bower/Grunt removed in favor of npm/esbuild
- Added ESM and IIFE bundle outputs with TypeScript declarations
- Supports jQuery 3.x and 4.x
- Keyboard accessibility for all interactive elements
- Full test suite using Vitest
- Upgraded jQuery to version 1.7.1
-
Fixed a bug when creating a new annotation via AJAX.
-
The Id of the annotation is expected to be returned as a JSON object from the response of the save call, e.g.
{ "annotation_id": "000001" }
- Fixed jQuery UI 1.3.2 compatibility.
- Forked source for jQuery 1.2.x and 1.3.x
- Notes now fade in/out.
- Tidied up CSS/positioning.
- Fixed bug when annotating an image with no previous annotations.
- Initial release
Based on the Drupal extension:
Image Annotations by Ronan Berder hunvreus@gmail.com http://drupal.org/project/image_annotate
Released under the GNU GPL v2 license.