In this quick start guide we will create a very simple application that enables us to annotate an image with arrows and then both display the annotation as overlay and render the image with annotations embedded.
Note: this quick start aims to demonstrate the core concepts in marker.js 3 and purposely ignores best practices and other considerations that would add to the bulk of code without contributing to helping you understand the main parts and principles.
This quick start is written in plain ES6/ES2015 and doesn't require any transpilers, bundlers or anything else to run. If you want a slightly more advanced quick start using Vite, refer to the TypeScript Quick Start and just ignore the TypeScript-specific code.
Keep in mind that just opening index.html
from a file system would likely be blocked by your browser's security settings. You will need to open it through a local HTTP server. If you are using VS Code you can use Live Server extension for a quick and simple local server solution. Alternatively, you can install http-server from npm or any other HTTP server of your choice.
We will need a sample image to annotate. You can use any image you want, but if you don't have one handy, just use this one:
Save it in the img
folder of the project. The rest of this tutorial assumes that you have a sample-image.png
in your img
directory.
First let's create a simple HTML page to hold our components. Create an index.html
file and add this HTML code to it:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/@markerjs/markerjs3/umd/markerjs3.js"></script>
<link rel="stylesheet" href="./style.css" />
<title>marker.js 3 JavaScript Quick Start</title>
</head>
<body>
<div id="app">
<button id="addArrowButton">Add Arrow</button>
<button id="saveButton">Save</button>
</div>
<script type="module" src="./main.js"></script>
</body>
</html>
The script
tag in the head
load marker.js 3 straight from npm via unpkg and the script
tag at the end of the body
refers to our custom code for this tutorial.
The core element of our application is the div
with id="app"
and two buttons inside of it.
<div id="app">
<button id="addArrowButton">Add Arrow</button>
<button id="saveButton">Save</button>
</div>
We are also referring a CSS file that can be anything (or nothing for that matter) but if you want to include the same CSS used in this demo, you can grab one from GitHub for consistency.
The core functionality will reside in the main.js
file. Create it next to index.html
and open in your code editor of choice.
First let's destructure the global markerjs3
object for convenience.
const { MarkerArea, ArrowMarker, MarkerView, Renderer } = markerjs3;
Let's create a target image object. We will need to pass it to the editor and later viewer and renderer.
const targetImg = document.createElement('img');
targetImg.src = './img/sample-image.png';
Let's store the reference to the app
div in the app
constant:
const app = document.querySelector('#app');
Now it's finally time to add the main annotation editor element - MarkerArea from the marker.js 3 package.
Create the markerArea
instance, assign our sample image to it, and add it to the page:
const markerArea = new MarkerArea();
markerArea.targetImage = targetImg;
app.appendChild(markerArea);
Now if you start the local server and navigate to the page you should see the sample image loaded. It doesn't do anything yet, though.
When the user clicks the "Add Arrow" button we want to initiate creation of an arrow marker. So, let's implement this.
Let's add an event handler to the button's click
event:
document.querySelector('#addArrowButton').addEventListener('click', () => {
markerArea.createMarker(ArrowMarker);
});
All it needs to do is call the MarkerArea.createMarker() method and pass a marker type to create.
createMarker()
works with strings as well, so you can pass "ArrowMarker"
in place of the type.
Now when you refresh the page and click on the "Add Arrow" button you should be able to draw arrows on the image.
In addition to the "Add Arrow" we've added a "Save" button to the page. Let's hook it up to an event handler and display the state from the editor in an annotation viewer.
For simplicity, let's add MarkerView instance to the page when it loads and hide it initially:
const markerView = new MarkerView();
markerView.targetImage = targetImg;
app.appendChild(markerView);
markerView.style.display = 'none';
Make sure you set its targetImage
property to the same image we used in the editor.
We are going to add an event listener to the "Save" button's click
event, get the state (annotation) from the editor, and show it in the viewer:
document.querySelector('#saveButton').addEventListener('click', () => {
// get marker area state (annotation)
const state = markerArea.getState();
// display the state in the viewer
markerView.style.display = '';
markerView.show(state);
});
Don't forget to make the viewer visible by resetting its display
style.
Saving and restoring annotation code is great when you work in an environment that includes marker.js. Oftentimes you'd need to export the annotations so you can share them with other people, archive, etc.
That's where the Renderer comes in. It takes the annotation state and renders it on top of the original target image. Let's add it to our code.
Again, for simplicity, let's pre-add an image element for the rendered annotation to the page and hide it initially:
const rasterImage = document.createElement('img');
app.appendChild(rasterImage);
rasterImage.style.display = 'none';
Now, we are going to extend our "Save" button click handler to render the image in addition to showing the viewer.
For this we will create an instance of the Renderer
class, set its targetImage
property to the same image we use everywhere and call its Renderer.rasterize() method:
const renderer = new Renderer();
renderer.targetImage = targetImg;
renderer.rasterize(state).then((dataUrl) => {
rasterImage.src = dataUrl;
rasterImage.style.display = '';
});
Note that rasterize()
is an async method. You can call it using the async/await notation or you can just go the "classic" promises way like we did here. The dataUrl
contains a base64 encoded rasterized image with annotations on top of the original. Now we just set it to the src
attribute of our image element and make it visible.
And that's it. This concludes our quick walkthrough. Now when you draw some arrows and click save you should see a vector overlay of the annotation and a rendered version underneath.
Here's the complete content of our main.js
file:
// Use global markerjs3 object
const { MarkerArea, ArrowMarker, MarkerView, Renderer } = markerjs3;
// create the target image element
const targetImg = document.createElement('img');
targetImg.src = './img/sample-image.png';
// app div
const app = document.querySelector('#app');
// create the marker area, assign the target image and append it to the app div
const markerArea = new MarkerArea();
markerArea.targetImage = targetImg;
app.appendChild(markerArea);
// add arrow marker button action
document.querySelector('#addArrowButton').addEventListener('click', () => {
markerArea.createMarker(ArrowMarker);
});
// add annotation viewer and hide it initially
const markerView = new MarkerView();
markerView.targetImage = targetImg;
app.appendChild(markerView);
markerView.style.display = 'none';
// add save button action
document.querySelector('#saveButton').addEventListener('click', () => {
// get marker area state (annotation)
const state = markerArea.getState();
// display the state in the viewer
markerView.style.display = '';
markerView.show(state);
// render the annotation to an image
const renderer = new Renderer();
renderer.targetImage = targetImg;
renderer.rasterize(state).then((dataUrl) => {
rasterImage.src = dataUrl;
rasterImage.style.display = '';
});
});
// image element for the rendered annotation
const rasterImage = document.createElement('img');
app.appendChild(rasterImage);
rasterImage.style.display = 'none';
And you can find this whole project on GitHub