Skip to content

Annotation Popups

A Popup that shows information about an annotation when the user clicks on it is a common requirement. Annotorious makes it easy to build your own custom popups in React with the ImageAnnotationPopup component.

Basic Example

Here’s a basic example of how to build a custom popup with the ImageAnnotationPopup component:

import {
Annotorious,
ImageAnnotationPopup,
ImageAnnotator
} from '@annotorious/react';
import './App.css';
import '@annotorious/react/annotorious-react.css';
export default function App() {
return (
<Annotorious>
<ImageAnnotator>
<img src="example.jpg" />
</ImageAnnotator>
<ImageAnnotationPopup
popup={(props: PopupContentProps) => (
<div>
Hello World
</div>
)} />
</Annotorious>
);
}

ImageAnnotationPopup is completely unstyled. Be sure to add CSS styles according to your needs.

Editing Body Payload

Popups aren not just for displaying information – they’re also great for editing annotation content. Annotorious follows the W3C Web Annotation Data Model, storing custom payload in AnnotationBody objects. The ImageAnnotationPopup component provides an easy way to manipulate these bodies through callback props.

  • onCreateBody(body: Partial<AnnotationBody>): adds a new body to the annotation.
  • onDeleteBody(id: string): removes a body from the anntation using its ID.
  • onUpdateBody(current: AnnotationBody, next: Partial<AnnotationBody>): modifies an existing body by replacing it with an updated version.

Example: Editable Comments

Let’s look at a more advanced popup that allows users to view and edit comments. In this example, we will store comments in a body with a commenting purpose.

import { useState, useEffect } from 'react';
import {
Annotorious,
ImageAnnotationPopup,
ImageAnnotator,
AnnotationBody
} from '@annotorious/react';
const CommentPopup = (props: PopupContentProps) => {
const { annotation, onCreateBody, onUpdateBody } = props;
const [comment, setComment] = useState('');
useEffect(() => {
const commentBody = annotation.bodies.find(body => body.purpose === 'commenting');
setComment(commentBody ? commentBody.value : '');
}, [annotation.bodies]);
const onSave = () => {
const updated = {
purpose: 'commenting',
value: comment
};
const commentBody = annotation.bodies.find(body => body.purpose === 'commenting');
if (commentBody) {
onUpdateBody(commentBody, updated);
} else {
onCreateBody(updated);
}
};
return (
<div>
<textarea
value={comment}
onChange={e => setComment(e.target.value)} />
<button onClick={onSave}>Save</button>
</div>
)
}
export default function App() {
return (
<Annotorious>
<ImageAnnotator>
<img src="example.jpg" />
</ImageAnnotator>
<ImageAnnotationPopup popup={CommentPopup} />
</Annotorious>
);
}

OpenSeadragon

Building popups for OpenSeadragon works exactly the same way. There is only one crucial difference: be sure to use the OpenSeadragonAnnotationPopup component instead of ImageAnnotationPopup. This component will also handle popup positioning correctly when zooming and panning the viewer.

import {
Annotorious,
OpenSeadragonAnnotationPopup,
OpenSeadragonAnnotator,
OpenSeadragonViewer
} from '@annotorious/react';
import './App.css';
import '@annotorious/react/annotorious-react.css';
export default function App() {
return (
<Annotorious>
<OpenSeadragonAnnotator>
<OpenSeadragonViewer options={{
tileSources: {
type: 'image',
url: '/images/sample-image.jpg'
}
}} />
</OpenSeadragonAnnotator>
<OpenSeadragonAnnotationPopup
popup={(props: PopupContentProps) => (
<div>
Hello World
</div>
)} />
</Annotorious>
);
}