Building Image Annotations with Annotorious.js
Installation
CDN
<!-- Stylesheet -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@recogito/annotorious@2.7.10/dist/annotorious.min.css">
<!-- JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/@recogito/annotorious@2.7.10/dist/annotorious.min.js"></script>
Alternatively, download these files locally and reference them in your project.
NPM
npm install @recogito/annotorious
Import in your project:
import { Annotorious } from '@recogito/annotorious'
import '@recogito/annotorious/dist/annotorious.min.css'
Basic Usage
After installation, using Annotorious involves two steps:
- Insert an image in your HTML
- Initialize Annotorious and bind it to the image element (by ID or referance)
The initialization syntax differs slightly between CDN and NPM approaches.
CDN
<img src="./photo.jpg" id="main-image" />
<script>
const anno = Annotorious.init({
image: 'main-image'
})
</script>
NPM
<img src="./photo.jpg" id="main-image" />
<script>
const anno = new Annotorious({
image: document.getElementById('main-image')
})
</script>
Note: Always initialize Annotorious within the onMounted/onload lifecycle hook of your framework.
Exporting Annotations
To persist annotations to a server, export the data using getAnnotations():
<button id="saveBtn">Save</button>
<img src="./photo.jpg" id="photo" />
<script>
let annotator = null
window.addEventListener('DOMContentLoaded', () => {
annotator = Annotorious.init({
image: 'photo'
})
})
document.getElementById('saveBtn').addEventListener('click', () => {
const annotations = annotator.getAnnotations()
console.log(annotations)
})
</script>
Loading Annotations from URL
Use loadAnnotations(url) to restore previously saved annotations:
[
{
"@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/photo.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"
}
]
<button id="loadBtn">Load</button>
<img src="./photo.jpg" id="photo" />
<script>
let annotator = null
window.addEventListener('DOMContentLoaded', () => {
annotator = Annotorious.init({
image: 'photo'
})
})
document.getElementById('loadBtn').addEventListener('click', () => {
annotator.loadAnnotations("http://127.0.0.1:5500/annotations.json")
})
</script>
Adding Annotations Programmatically
When the backend returns JSON data directly rather than a file URL, use addAnnotation() to render each annotation:
<button id="loadBtn">Load</button>
<img src="./photo.jpg" id="photo" />
<script>
let annotator = null
window.addEventListener('DOMContentLoaded', () => {
annotator = Annotorious.init({
image: 'photo'
})
})
document.getElementById('loadBtn').addEventListener('click', () => {
fetch('http://localhost:3000/annotations')
.then(response => response.json())
.then(data => {
data.data.forEach(item => {
annotator.addAnnotation(item)
})
})
})
</script>
Configuration Options
Locale
Annotorious automatically detects the browser language. Override this with the locale option:
const anno = Annotorious.init({
image: 'main-image',
locale: 'zh-CN'
})
Custom Messages
Replace default UI text by configuring the messages object:
const customMessages = {
"Add a comment...": "Add comment",
"Add a reply...": "Add reply",
"Add tag...": "Add tag",
"Cancel": "Cancel",
"Close": "Close",
"Edit": "Edit",
"Delete": "Delete",
"Ok": "Confirm"
}
const anno = Annotorious.init({
image: 'main-image',
messages: customMessages
})
When both locale and messages are configured, messages takes precedence.
Allow Empty Annotations
By default, selections without comments or tags are discarded. Enable allowEmpty to keep empty selections:
const anno = Annotorious.init({
image: 'main-image',
allowEmpty: true
})
Crosshair Guide
Enable the crosshair for pixel-precise selections:
const anno = Annotorious.init({
image: 'main-image',
crosshair: true
})
Read-Only Mode
Prevent all user interactions (selection, creation, deletion):
const anno = Annotorious.init({
image: 'main-image',
readOnly: true
})
Disable Editor
To allow drawing selections without adding comments, enable both allowEmpty and disableEditor:
const anno = Annotorious.init({
image: 'main-image',
allowEmpty: true,
disableEditor: true
})
Setting allowEmpty: true is necessary because selections without content would otherwise disappear.
Disable Selection
Prevent existing annotations from being selected:
const anno = Annotorious.init({
image: 'main-image',
disableSelect: true
})
Handle Radius
The default handle radius is 6 pixels. Adjust it with handleRadius:
const anno = Annotorious.init({
image: 'main-image',
handleRadius: 20
})
Custom Styling
Annotations render using SVG, while the editor uses HTML elements. Both can be styled via CSS:
<style>
/* Selection 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;
}
/* Dashed border */
svg.a9s-annotationlayer .a9s-selection .a9s-inner,
svg.a9s-annotationlayer .a9s-annotation .a9s-inner {
stroke-width: 4;
stroke: white;
stroke-dasharray: 5;
}
/* Hover fill */
svg.a9s-annotationlayer .a9s-annotation.editable:hover .a9s-inner {
fill: transparent;
}
/* Handle styling */
svg.a9s-annotationlayer .a9s-handle .a9s-handle-inner {
fill: white;
stroke: white;
}
/* Selection mask overlay */
svg.a9s-annotationlayer .a9s-selection-mask {
fill: rgba(0, 0, 0, 0.6);
}
/* Editor container */
.r6o-editor .r6o-editor-inner {
box-sizing: border-box;
padding: 10px;
border-radius: 6px;
background: #F4F2DE;
}
/* Arrow */
.r6o-editor .r6o-arrow:after {
background-color: #F4F2DE;
}
/* Widgets */
.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;
}
/* Buttons */
.r6o-editor .r6o-btn {
border-radius: 100px;
background-color: #7C9D96;
border-color: #7C9D96;
color: #fff;
}
/* Outline buttons */
.r6o-editor .r6o-btn.outline {
color: #7C9D96;
background-color: transparent;
}
</style>
<img src="./photo.jpg" id="main-image" />
<script>
const anno = Annotorious.init({
image: 'main-image'
})
</script>
Widget Configuration
Add predefined tag suggestions for faster input:
const anno = Annotorious.init({
image: 'main-image',
widgets: [
'COMMENT',
{ widget: 'TAG', vocabulary: ['Important', 'Review', 'Question'] }
]
})
Polygon Tool
Switch drawing tools using setDrawingTool():
const anno = Annotorious.init({
image: 'main-image'
})
anno.setDrawingTool('polygon')
List available drawing tools:
const tools = anno.listDrawingTools()
console.log(tools)
Additional Resources
For comprehensive API documentation, refer to the official Annotorious API reference.
Plugin ecosystem and additional features are documented on the Annotorious plugins page.