Overview
Recently, a colleague asked how to annotate images using frontend technologies. While libraries like Fabric.js or Konva.js can achieve this, I was curious if there are specialized tools designed specifically for image annotation.
Upon searching online, I discovered Annotorious, which offers image annotation and tagging capabilities with an easy-to-use interface.
This article covers two sections: "Quick Start" and "API Reference." The first section explains installation, usage, and data import/export processes—core aspects useful for rapid implementation. The second part highlights commonly used features based on personal experience.
Quick Start
This section details installing, initializing, importing, and exporting data with Annotorious.
Installing Annotorious
CDN
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@recogito/annotorious@2.7.10/dist/annotorious.min.css">
<script src="https://cdn.jsdelivr.net/npm/@recogito/annotorious@2.7.10/dist/annotorious.min.js"></script>
Alternatively, download these files into your project directory and reference them locally.
NPM
Install via npm:
npm install @recogito/annotorious
Then import in your project:
import { Annotorious } from '@recogito/annotorious'
import '@recogito/annotorious/dist/annotorious.min.css'
Using Annotorious
After installation, initialize Annotorious with two simple steps:
- Insert an image element in HTML.
- Initialize
Annotoriouswith the image element (by ID or DOM node).
Initialization varies slightly between CDN and NPM setups.
CDN Initialization
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image'
})
</script>
NPM Initialization
<img src="./img.jpg" id="my-image" />
<script>
const anno = new Annotorious({
image: document.getElementById('my-image')
})
</script>
Note: Place initialization code with in the lifecycle hooks of your framework after the page has fully loaded.
Exporting Annotations: getAnnotations()
To save annotations to a server, use getAnnotations().
<button onclick="save()">Save</button>
<img src="./44.jpg" id="img" />
<script>
let anno = null;
onload = function() {
anno = Annotorious.init({
image: 'img'
});
};
function save() {
let res = anno.getAnnotations();
console.log(res);
}
</script>
Importing Annotations: loadAnnotations(url)
Data can be imported using loadAnnotations(url), which accepts a URL pointing to a data file.
Suppose we have a data.json file containing annotations exported via getAnnotations():
[
{
"@context": "http://www.w3.org/ns/anno.jsonld",
"type": "Annotation",
"body": [
{
"type": "TextualBody",
"value": "1",
"purpose": "commenting"
}
],
"target": {
"source": "http://127.0.0.1:5500/44.jpg",
"selector": {
"type": "FragmentSelector",
"conformsTo": "http://www.w3.org/TR/media-frags/",
"value": "xywh=pixel:100,100,500,300"
}
},
"id": "#cabe2e71-b19f-4499-80c6-235882fd50ba"
}
]
Use a local server to serve data.json. Then load it:
<button onclick="load()">Load</button>
<img src="./44.jpg" id="img" />
<script>
let anno = null;
onload = function() {
anno = Annotorious.init({
image: 'img'
});
};
function load() {
anno.loadAnnotations("http://127.0.0.1:5500/data.json");
}
</script>
Clicking the Load button will render the annotation box with its content.
Adding Annotations: addAnnotation()
In real-world projects, backend services may return JSON data instead of a file path. To handle such cases, iterate through the data and call addAnnotation().
Using json-server and axios for demonstration:
<button onclick="load()">Load</button>
<img src="./44.jpg" id="img" />
<script>
let anno = null;
onload = function() {
anno = Annotorious.init({
image: 'img'
});
};
function load() {
axios.get('http://localhost:3000/anno')
.then(res => {
res.data.data.forEach(item => {
anno.addAnnotation(item);
});
});
}
</script>
API Reference
This section discusses frequently used functionalities of Annotorious. For full documentation, refer to the official site.
Localization: locale
The default language depends on browser settings.
Override it by setting locale during initialization:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
locale: 'zh-CN'
});
</script>
Custom Messages: messages
Customize UI text by passing a messages object:
<img src="./img.jpg" id="my-image" />
<script>
var customMessages = {
"Add a comment...": "Add comment",
"Add a reply...": "Reply here",
"Add tag...": "Enter tag",
"Cancel": "Cancel",
"Close": "Close",
"Edit": "Edit",
"Delete": "Delete",
"Ok": "OK"
};
let anno = Annotorious.init({
image: 'my-image',
messages: customMessages
});
</script>
If both locale and messages are set, messages takes precedence.
Empty Annotations: allowEmpty
By default, empty annotations are not saved. Set allowEmpty to true to enable saving them:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
allowEmpty: true
});
</script>
Crosshair: crosshair
Enable precise selection by activating crosshair:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
crosshair: true
});
</script>
Read-only Mode: readOnly
Disable editing features by setting readOnly to true:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
readOnly: true
});
</script>
Disable Editor: disableEditor
To allow drawing without comments, set both disableEditor and allowEmpty to true:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
allowEmpty: true,
disableEditor: true
});
</script>
Disable Selection: disableSelect
Prevent selecting existing annotations by enabling disableSelect:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
disableSelect: true
});
</script>
Handle Radius: handleRadius
Adjust the size of handles with handleRadius:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
handleRadius: 20
});
</script>
Custom Styles
Styling can be applied to annotations and editors using CSS. Annotations are rendered via SVG, while editors use standard HTML elements.
Example custom styles:
/* Annotation styling */
svg.a9s-annotationlayer .a9s-selection .a9s-outer,
svg.a9s-annotationlayer .a9s-annotation .a9s-outer {
display: none;
}
svg.a9s-annotationlayer .a9s-handle .a9s-handle-outer {
display: none;
}
svg.a9s-annotationlayer .a9s-selection .a9s-inner,
svg.a9s-annotationlayer .a9s-annotation .a9s-inner {
stroke-width: 4;
stroke: white;
stroke-dasharray: 5;
}
svg.a9s-annotationlayer .a9s-annotation.editable:hover .a9s-inner {
fill: transparent;
}
svg.a9s-annotationlayer .a9s-handle .a9s-handle-inner {
fill: white;
stroke: white;
}
svg.a9s-annotationlayer .a9s-selection-mask {
fill: rgba(0, 0, 0, 0.6);
}
/* Editor styling */
.r6o-editor .r6o-editor-inner {
box-sizing: border-box;
padding: 10px;
border-radius: 6px;
background: #F4F2DE;
}
.r6o-editor .r6o-arrow:after {
background-color: #F4F2DE;
}
.r6o-widget.comment.editable,
.r6o-widget.r6o-tag {
background-color: #EEE3CB;
}
.r6o-widget.comment {
background-color: #D7C0AE;
}
.r6o-editor .r6o-editor-inner .r6o-widget {
border-bottom-color: #7C9D96;
}
.r6o-editor .r6o-editor-inner .r6o-widget.r6o-tag {
border-bottom: none;
}
.r6o-editor .r6o-btn {
border-radius: 100px;
background-color: #7C9D96;
border-color: #7C9D96;
color: #fff;
}
.r6o-editor .r6o-btn.outline {
color: #7C9D96;
background-color: transparent;
}
Predefined Widgets
Configure predefined options during input:
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image',
widgets: [
'COMMENT',
{ widget: 'TAG', vocabulary: ['tag1', 'tag2', 'tag3'] }
]
});
</script>
Polygon Drawing
Change drawing tools with setDrawingTool():
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image'
});
anno.setDrawingTool("polygon");
</script>
List available tools using listDrawingTools():
<img src="./img.jpg" id="my-image" />
<script>
let anno = Annotorious.init({
image: 'my-image'
});
const toolNames = anno.listDrawingTools();
console.log(toolNames);
</script>
Additional Features
Beyond the mentioned APIs, Annotorious supports deleting specific annotations or clearing all annotations. Refer to the official API documentation for more.
Plugins
Explore additional functionality through plugins listed in the Annotorious plugin recommendations.