edirom-web-components

GitHub License

Edirom Image Viewer Component

This web component displays IIIF images using the OpenSeadragon library. It is intended to be used in the Edirom Online, but can also be (re-)used in other web applications. No compilation or building is necessary to use the web component.

Note: This repository only contains the bare JavaScript-based component. There is a separate demo suite for web components developed in the Edirom Online Reloaded project, where the component can be seen and tested.

Features

License

The edirom-image-viewer.js comes with the MIT license.

The imported OpenSeadragon library comes with the BSD-3-Clause license.

How to Use This Web Component

1. Include the Component

Add the web component script to your HTML page’s <head>:

<script src="https://cdn.jsdelivr.net/npm/openseadragon@4.1.1/build/openseadragon/openseadragon.min.js"></script>
<script src="path/to/edirom-image-viewer.js"></script>

2. Add the Custom Element

Include the custom element in your HTML <body>:

<edirom-image-viewer 
    tilesources='["https://example.com/iiif/manifest.json"]'
    shownavigator="true"
    showzoomcontrol="true"
    sequencemode="true">
</edirom-image-viewer>

3. Interact via Attributes

Control the component by setting attributes programmatically:

const viewer = document.querySelector('edirom-image-viewer');
viewer.setAttribute('zoom', '2.5');
viewer.setAttribute('rotation', '90');
viewer.setAttribute('pagenumber', '5');

4. Listen to Events

The component fires custom events when state changes:

viewer.addEventListener('communicate-zoom-update', (event) => {
    console.log('Zoom changed:', event.detail);
});

Attributes

Note: All attribute values are strings. The data type information indicates the expected format.

Important Note on Page Numbering: Page numbers are 1-based for user-facing interactions. This means:

This applies to pagenumber attribute and all page-related methods. The component automatically converts between 1-based (user) and 0-based (internal) indexing.

Attribute Type Description Default
tilesources string JSON array of IIIF manifest URLs or tile source URLs. Example: '["https://example.com/manifest.json"]' or '["https://example.com/info.json"]' ""
pagenumber number Current page number in a multi-image sequence (1-based, where 1 = first image). 1
zoom number Zoom level of the viewer. Values are clamped to [minzoomlevel, maxzoomlevel]. 1
rotation number Rotation angle in degrees (0-360). 0
preserveviewport boolean Preserve the current viewport (zoom/pan) when changing pages. false
clicktozoom boolean Enable click-to-zoom functionality. true
minzoomlevel number Minimum allowed zoom level. Programmatic zoom is clamped to this lower bound. OSD default
maxzoomlevel number Maximum allowed zoom level. Programmatic zoom is clamped to this upper bound. OSD default
shownavigationcontrol boolean Show/hide all navigation controls. true
sequencemode boolean Enable sequence mode for multi-image navigation. false
shownavigator boolean Show/hide the navigator mini-map. true
showzoomcontrol boolean Show/hide zoom in/out buttons. true
showhomecontrol boolean Show/hide the home/reset view button. true
showfullpagecontrol boolean Show/hide the fullscreen toggle button. true
showsequencecontrol boolean Show/hide previous/next page buttons (requires sequencemode="true"). true
triggerhome boolean Trigger home position reset (set to "true" to reset view to initial state). "false"
triggerfullscreen boolean Trigger fullscreen mode toggle (set to "true" to toggle fullscreen). "false"
openseadragon-options string JSON object with additional OpenSeadragon configuration options. Example: '{"showNavigator": true}' ""
zones-data string JSON object mapping zone keys to zone objects. Each zone: { page: number, ulx: number, uly: number, lrx: number, lry: number }. See Region Navigation for details. "{}"
zone string Key of the zone to navigate to. Must exist in zones-data. Setting this attribute triggers navigation to the zone. ""
measures-data string JSON object mapping measure keys to region objects { page, ulx, uly, lrx, lry }. Lookup map for measure. See Region Navigation. "{}"
measure string Key of the measure to navigate to (must exist in measures-data). Append \|<nonce> to re-fire navigation to the same measure. ""
mdivs-data string JSON object mapping movement (mdiv) keys to objects { page } (with optional region). Lookup map for mdiv. See Region Navigation. "{}"
mdiv string Key of the movement to navigate to (must exist in mdivs-data). Append \|<nonce> to re-fire navigation to the same movement. ""
annotations-data string JSON array of annotation overlays. Each entry: { idPrefix, id, title, uri, categories, priority, fn, tooltip, plist }, where tooltip is optional host-supplied HTML rendered by the component on hover and plist is an array of image-pixel regions { id, ulx, uly, lrx, lry, type }. Rendered as clickable badges. See Annotation Overlays. "[]"
show-annotations boolean Show/hide the rendered annotation overlays. Toggles visibility without discarding annotations-data; the last state persists across page changes. false
visible-categories string JSON array of category ids that should remain visible. ["undefined"] (no category taxonomy) or absent shows all; [] hides all; otherwise a badge is shown only if one of its categories is listed. See Annotation Overlays. null
visible-priorities string JSON array of priority ids that should remain visible. Same ["undefined"] / [] / list semantics as visible-categories. A badge is shown only when it passes both filters. null
measure-numbers-data string JSON array of measure-number overlay boxes. Each entry: { idPrefix, id, name, ulx, uly, lrx, lry }. Rendered as labelled boxes on the current image. See Measure Number Overlays. "[]"
show-measure-numbers boolean Show/hide the rendered measure-number overlays. Toggles visibility without discarding measure-numbers-data; the last state persists across page changes. false
fitrect string Fit the viewport to an image-pixel rectangle "x,y,width,height". An optional trailing ,<nonce> token re-fires the same fit. ""
view-mode string Declarative view mode (e.g. pageBasedView / measureBasedView). Recorded and re-broadcast via the view-mode-changed event for host code to react to. ""

Public Methods

The component provides the following public methods:

Zoom

View Control

Rotation

Examples

Basic IIIF Manifest

<edirom-image-viewer 
    tilesources='["https://example.com/iiif/manifest.json"]'>
</edirom-image-viewer>

Multi-Page Sequence with Controls and Trigger Attributes

<edirom-image-viewer 
    id="viewer"
    tilesources='["https://content.staatsbibliothek-berlin.de/dc/69007087X-0001/info.json", "https://content.staatsbibliothek-berlin.de/dc/69007087X-0002/info.json"]'
    pagenumber="1"
    zoom="1"
    rotation="0"
    triggerhome="false"
    triggerfullscreen="false"
    sequencemode="true"
    showsequencecontrol="true"
    shownavigator="true"
    showzoomcontrol="true"
    showhomecontrol="true"
    showfullpagecontrol="true">
</edirom-image-viewer>

Controlling via JavaScript and Triggers

const viewer = document.querySelector('edirom-image-viewer');

// Navigate to page 3
viewer.setAttribute('pagenumber', '3');

// Zoom to level 2
viewer.setAttribute('zoom', '2');

// Reset to home position
viewer.setAttribute('triggerhome', 'true');

// Toggle fullscreen
viewer.setAttribute('triggerfullscreen', 'true');

Custom OpenSeadragon Configuration

IIIF Support

The component supports both IIIF manifests and direct image tile sources:

Trigger Attributes

Trigger attributes are used to invoke actions on the viewer. Set these attributes to "true" to trigger the corresponding action:

Example:

const viewer = document.querySelector('edirom-image-viewer');
viewer.setAttribute('triggerhome', 'true');      // Reset to home position
viewer.setAttribute('triggerfullscreen', 'true'); // Toggle fullscreen

Events

The component fires a generic communicate-[property]-update event whenever any observed attribute changes:

The component also fires dedicated semantic events:

Event Detail Fired when
page-changed { pageNumber } (1-based) The viewer navigates to a new page.
zone-changed { zoneKey, zone } Navigation to a zone completes.
measure-changed { key, region } Navigation to a measure completes.
mdiv-changed { key, region } Navigation to an mdiv completes.
view-mode-changed { viewMode } The view-mode attribute changes.
zoom { zoom } The OpenSeadragon viewport zoom changes.
image-ready — The image/tiles have finished loading.
annotation-click { id, uri, fn, title, element } An annotation badge is clicked.
annotation-mouseenter / annotation-mouseleave { id, uri, fn, title, element } The pointer enters/leaves an annotation badge.
annotation-filter-changed { visibleCategories, visiblePriorities } visible-categories or visible-priorities changes (incl. externally).
viewer.addEventListener('page-changed', (event) => {
    console.log('Navigated to page:', event.detail.pageNumber);
});

viewer.addEventListener('measure-changed', (event) => {
    console.log('Navigated to measure:', event.detail.key);
});

Region Navigation

The component supports pixel-precise navigation to named rectangular regions on any page, independent of OSD’s own sequence controls. Three parallel lookup maps share the same mechanism and the same region object shape:

Lookup map Trigger attribute Completion event Typical use
zones-data zone zone-changed Generic named regions
measures-data measure measure-changed Music measures / bars
mdivs-data mdiv mdiv-changed MEI <mdiv> movements (page-level, optional region)

Region Object Format

Each entry in a *-data map must have a 1-based page number and (for precise fits) pixel coordinates (ulx, uly, lrx, lry) defining the upper-left and lower-right corners of the region. Movements (mdivs-data) may carry only a page to navigate to the movement’s first page without a region fit.

{
  "measure_1": { "page": 1, "ulx": 100, "uly": 200, "lrx": 800, "lry": 600 },
  "measure_2": { "page": 1, "ulx": 900, "uly": 200, "lrx": 1600, "lry": 600 },
  "measure_3": { "page": 2, "ulx": 150, "uly": 300, "lrx": 950, "lry": 700 }
}

Push model: data map + trigger

The *-data attribute is a lookup map (set once, performs no navigation on its own). The matching trigger attribute (zone / measure / mdiv) is the navigation trigger and must hold a key that exists in the map. To re-fire navigation to the same key, append a |<nonce> token to the trigger value — it is stripped before lookup:

let nonce = 0;
viewer.setAttribute('measure', 'measure_1|' + (++nonce)); // jump
viewer.setAttribute('measure', 'measure_1|' + (++nonce)); // jump again to the same measure

Empty trigger values (measure="", mdiv="") are ignored, so they are safe as defaults in markup.

Example: Measure Navigation

<edirom-image-viewer
    id="viewer"
    sequencemode="true"
    showsequencecontrol="false"
    tilesources='[...]'
    measures-data='{}'
    measure="">
</edirom-image-viewer>
const viewer = document.querySelector('#viewer');

// Populate the lookup map
viewer.setAttribute('measures-data', JSON.stringify({
    measure_1: { page: 1, ulx: 100, uly: 200, lrx: 800, lry: 600 },
    measure_2: { page: 2, ulx: 150, uly: 300, lrx: 950, lry: 700 }
}));

// Trigger navigation
viewer.setAttribute('measure', 'measure_2');

viewer.addEventListener('measure-changed', (event) => {
    console.log('Navigated to measure:', event.detail.key);
});

viewer.addEventListener('page-changed', (event) => {
    console.log('Page is now:', event.detail.pageNumber);
});

Rectangle Fit (fitrect)

Fit the viewport to an arbitrary image-pixel rectangle, independent of any lookup map. The value is "x,y,width,height" in image-pixel coordinates, with an optional trailing ,<nonce> token to re-fire the same fit:

viewer.setAttribute('fitrect', '500,300,1200,800');

View Mode (view-mode)

A declarative attribute the host can set to record the active view mode (e.g. pageBasedView / measureBasedView). The component stores it and re-broadcasts it via the view-mode-changed event; the actual layout swap is owned by the surrounding host application:

viewer.setAttribute('view-mode', 'measureBasedView');
viewer.addEventListener('view-mode-changed', (event) => {
    console.log('View mode:', event.detail.viewMode);
});

Measure Number Overlays

In addition to navigating to measures (see Region Navigation), the component can render labelled measure-number boxes directly on top of the current image. This uses a push/persist model: the host pushes the full set of boxes via measure-numbers-data, and toggles their visibility via show-measure-numbers. The component owns all rendering, showing, hiding and hover highlighting — the host never touches the DOM.

Data Format

measure-numbers-data is a JSON array of overlay descriptors. Each entry defines one box in image-pixel coordinates:

[
  { "idPrefix": "viewer1", "id": "m1", "name": "1", "ulx": 100, "uly": 100, "lrx": 200, "lry": 300 },
  { "idPrefix": "viewer1", "id": "m2", "name": "2", "ulx": 220, "uly": 100, "lrx": 320, "lry": 300 }
]
Field Description
idPrefix Prefix used (with id) to build the overlay’s unique DOM id.
id Measure id, combined with idPrefix into idPrefix_id.
name Label shown inside the box (the measure number). An empty name is rendered with an “empty” style.
ulx,uly Upper-left corner of the box, in image pixels.
lrx,lry Lower-right corner of the box (box size is lrx-ulx × lry-uly).

Persistent Show/Hide

show-measure-numbers toggles the overlays’ visibility (not display), so the pushed data is never discarded. The component remembers the last state and re-applies it to every freshly rendered page, so toggling once persists across page navigation until toggled again. Pushing a new measure-numbers-data re-renders the boxes and re-applies the current visibility.

const viewer = document.querySelector('edirom-image-viewer');

// Push the measure boxes for the current page
viewer.setAttribute('measure-numbers-data', JSON.stringify([
    { idPrefix: 'viewer1', id: 'm1', name: '1', ulx: 100, uly: 100, lrx: 200, lry: 300 },
    { idPrefix: 'viewer1', id: 'm2', name: '2', ulx: 220, uly: 100, lrx: 320, lry: 300 }
]));

// Show them
viewer.setAttribute('show-measure-numbers', 'true');

// Hide them (data is kept, just hidden)
viewer.setAttribute('show-measure-numbers', 'false');

Note: measure-numbers-data / show-measure-numbers (overlay rendering) are independent of measures-data / measure (region navigation). They can be used together or separately.

Annotation Overlays

The component renders clickable annotation badges on top of the current image using the same push/persist model as the measure-number overlays: the host pushes the full set of annotations via annotations-data, toggles their visibility via show-annotations, and narrows them down by category/priority via visible-categories / visible-priorities. The component owns all rendering, showing, hiding and filtering — the host never touches the DOM.

annotations-data format

annotations-data is a JSON array of annotation descriptors. Annotations pointing at the same region share one stacked container, and each badge carries the CSS classes annotIcon {categories} {priority} {type} so edition stylesheets can target them:

viewer.setAttribute('annotations-data', JSON.stringify([
    {
        idPrefix: 'viewer1',
        id: 'annot1',
        title: 'Slur added',
        uri: 'xmldb:exist:///db/.../annot1.xml',
        categories: 'wega.annotation.category.bogensetzung',
        priority: 'ediromAnnotPrio1',
        fn: '',                       // host click action (opaque to the component)
        tooltip: '<div class="annotTip">…host-supplied HTML…</div>', // rendered by the component on hover
        plist: [                      // one or more image-pixel regions
            { id: 'm1', ulx: 100, uly: 100, lrx: 160, lry: 160, type: 'measure' }
        ]
    }
]));

viewer.setAttribute('show-annotations', 'true');

Each badge dispatches annotation-click, annotation-mouseenter and annotation-mouseleave CustomEvents (with detail = { id, uri, fn, title, element }). The host uses annotation-click (via the opaque fn) for its click behaviour. The tooltip is rendered by the component itself: if an annotation carries a tooltip HTML string, the component shows it in a positioned, reusable tooltip element on hover and hides it on leave — the host only supplies the HTML (and may still listen to the mouse events for its own highlighting).

Category & priority filtering

visible-categories and visible-priorities are JSON arrays of the category / priority ids that should remain visible. A badge is shown only when it passes both filters (one of its categories is listed and its priority is listed). The sentinel values mirror the host’s filter menus:

Value Meaning
absent / null no filter pushed yet — show all
["undefined"] the edition has no such taxonomy — show all
[] every item unchecked — hide all
["catA", "catB"] show only badges whose category/priority is listed
// show only the "bogensetzung" category, any priority
viewer.setAttribute('visible-categories', JSON.stringify(['wega.annotation.category.bogensetzung']));
viewer.setAttribute('visible-priorities', JSON.stringify(['undefined']));

Filtering hides individual badges via display, and a stacked container is hidden once none of its badges pass the filter. Both show-annotations and the filters toggle visibility (not display) at the container level so OpenSeadragon redraws don’t override the hide. The chosen show/hide state and filter are remembered and re-applied to every freshly rendered page, so they persist across page navigation until changed.

Whenever visible-categories or visible-priorities changes (including when set externally, e.g. via DevTools), the component dispatches an annotation-filter-changed CustomEvent with detail = { visibleCategories, visiblePriorities } (the current filter arrays, or null for “no filter”). The host can listen for it to keep its own filter UI (e.g. menu checkboxes) in sync with the component’s state.

Browser Support

The component uses modern web standards (Custom Elements, Shadow DOM) and requires a modern browser with ES6+ support