Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building Image Annotations with Annotorious.js

Tech 1

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:

  1. Insert an image in your HTML
  2. 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.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.