Overview
I needed to change the zoom, scale, and rotation behavior of Mirador 4, so this is a memo on how to make those changes.
Setup
Start Mirador 4 locally with the following commands.
git clone https://github.com/projectmirador/mirador
cd mirador
pnpm i
pnpm start
It starts on port 4444.

Customizing the zoomIn Processing
As an example, let’s modify the processing when the zoomIn button is clicked as follows.
...
handleZoomInClick() {
const { windowId, updateViewport, viewer } = this.props;
updateViewport(windowId, {
// zoom: viewer.zoom * 2,
zoom: viewer.zoom * 1.1, // added
rotation: viewer.rotation + 5, // added
x: viewer.x * 1.1, // added
y: viewer.y * 1.1, // added
});
}
...
As a result, when pressing the zoomIn button, you can see that zoom and rotation occur while the center shifts slightly.
By applying this approach, you can customize the zoom, scale, rotation, and other behaviors of Mirador 3.
Setting the immediately Property
In the above example, it took a bit of time for zoom and rotation to complete. To perform these operations instantly, customize the componentDidUpdate in OpenSeadragonViewer.js as follows.
This allows configuring whether zoom and rotation should be performed immediately based on the immediately property of the viewerConfig object.
...
componentDidUpdate(prevProps, prevState) {
const {
viewerConfig,
canvasWorld,
} = this.props;
const { viewer } = this.state;
this.apiRef.current = viewer;
if (prevState.viewer === undefined) {
if (viewerConfig) {
viewer.viewport.panTo(viewerConfig, true);
viewer.viewport.zoomTo(viewerConfig.zoom, viewerConfig, true);
viewerConfig.degrees !== undefined && viewer.viewport.setRotation(viewerConfig.degrees);
viewerConfig.flip !== undefined && viewer.viewport.setFlip(viewerConfig.flip);
}
this.addAllImageSources(!(viewerConfig));
return;
}
if (!this.infoResponsesMatch(prevProps.infoResponses)
|| !this.nonTiledImagedMatch(prevProps.nonTiledImages)
) {
viewer.close();
const canvasesChanged = !(isEqual(canvasWorld.canvasIds, prevProps.canvasWorld.canvasIds));
this.addAllImageSources((canvasesChanged || !viewerConfig));
} else if (!isEqual(canvasWorld.layers, prevProps.canvasWorld.layers)) {
this.refreshTileProperties();
} else if (viewerConfig && !this.osdUpdating) {
const { viewport } = viewer;
const immediately = viewerConfig.immediately || false;
if (viewerConfig.x !== viewport.centerSpringX.target.value
|| viewerConfig.y !== viewport.centerSpringY.target.value) {
viewport.panTo(viewerConfig, immediately);
}
if (viewerConfig.zoom !== viewport.zoomSpring.target.value) {
viewport.zoomTo(viewerConfig.zoom, viewerConfig, immediately);
}
if (viewerConfig.rotation !== viewport.getRotation()) {
viewport.setRotation(viewerConfig.rotation, immediately);
}
if (viewerConfig.flip !== viewport.getFlip()) {
viewport.setFlip(viewerConfig.flip);
}
}
}
...
Then, add immediately: true to the earlier ZoomControls.js.
...
handleZoomInClick() {
const { windowId, updateViewport, viewer } = this.props;
updateViewport(windowId, {
// zoom: viewer.zoom * 2,
zoom: viewer.zoom * 1.1,
rotation: viewer.rotation + 5,
x: viewer.x * 1.1,
y: viewer.y * 1.1,
immediately: true, // added
});
}
...
The result is the following behavior.
Compared to the previous behavior, zoom and rotation are now performed immediately.
Why Instant Zoom and Rotation is Needed
I am currently developing a plugin that synchronizes zoom, scale, and rotation operations across multiple windows.
During this development, there were cases where synchronization did not work properly unless zoom, scale, and rotation operations were performed immediately.
There may be aspects that were not fully considered in this modification, but I hope to submit a pull request eventually.
Summary
We hope this serves as a useful reference for Mirador 4 plugin development.