The Annotations API lets you deeply integrate Cord's annotation functionality into your product, creating a seamless experience.
By supplying the "glue code" between the annotation flow and your UI, you ensure annotations always match what's on the screen. It's perfect for complex features with multiple states, like charts, text editors, maps, and more.
The most important concept behind the Annotations API is the annotation location. A Location is a flat object you associate with a part of your document, that describes, in your app's terminology, what is being annotated. Cord stores this Location object alongside the annotation, and we surface it when it's time to render the annotation pin.
For example, in a chart component (either written entirely by you or using a library like Highcharts, Chart.js, etc), you might describe the point being annotated as:
{
"page": "dashboard",
"chart": "revenue",
"month": 3,
"year": 2022
}
Or, for a video player, you might describe an annotation taken at timestamp 1:25
as:
{
"page": "dashboard",
"video": "dramatic-gopher.mp4",
"time": 85
}
An annotation target is an HTML element in your application's DOM decorated with a data-cord-annotation-location
attribute, with the value being a stable serialization of the location object that best represents that area of the document.
When the user starts the annotation flow from the Cord sidebar and clicks to place the annotation pin somewhere on the page, we check if the click is within an annotation target, by checking its DOM ancestors for any data-cord-annotation-location
attribute.
The useCordAnnotationTargetRef
hook helps you decorate HTML elements with thedata-cord-annotation-location
attribute representing the location for annotations captured within that element (and its DOM subtree). This hook handles serializing the location object and attaching it to the element as the data attribute so you don't have to.
import { useCordAnnotationTargetRef } from "@cord-sdk/react";
function VideoPlayerComponent({ videoSrc }) {
const location = { page: "dashboard", video: videoSrc };
const videoElementRef = useCordAnnotationTargetRef(location);
return <video src={videoSrc} ref={videoElementRef} />;
}
With just this addition to your DOM, any time the user places an annotation on the <video> element, we store the annotation location as, for example, { page: "dashboard", video: "dramatic-gopher.mp4" }
. When other users see that annotation, it will point to an element in the page that matches that location.
If your annotations need to include extra location information that is highly dynamic, dependent on where exactly the user clicked, or otherwise can't be expressed at render time, you can provide that through theuseCordAnnotationCaptureHandler
hook.
For example, you might want to remember the timestamp the video annotation was taken at, so that you can skip the video back to that exact timestamp when the user clicks the annotation in Cord.
import {
useCordAnnotationTargetRef,
useCordAnnotationCaptureHandler,
} from "@cord-sdk/react";
type VideoPlayerAnnotation = {
page: string;
video: string;
time: number;
};
function VideoPlayerComponent({ videoSrc }) {
// The initial location information for any annotations
// users create.
const location = { page: "dashboard", video: videoSrc };
// A ref to apply to your video element. Cord will
// use this ref when users create annotations, applying
// the `location` object to the annotation.
const videoElementRef = useCordAnnotationTargetRef(location);
// The callback provided here will be used to enrich the
// `location` data for the annotation. You would use
// this hook when you want to put more specific location
// information in the annotation. Here, for instance,
// we're adding the current time of the video player
// as extra `location` data.
useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
return {
extraLocation: {
time: videoElementRef.current?.currentTime ?? 0,
},
};
});
return <video ref={videoElementRef} src={videoSrc} />;
}
The useCordAnnotationCaptureHandler
hook can also be used to define a location-specific label for the annotation -- user-visible text that describes what is being annotated -- by returning a label
field. If you don't provide one, we default to "Annotation".
import {
useCordAnnotationTargetRef,
useCordAnnotationCaptureHandler,
} from "@cord-sdk/react";
type VideoPlayerAnnotation = {
page: string;
video: string;
time: number;
};
function VideoPlayerComponent({ videoSrc }) {
// The initial location information for any annotations
// users create.
const location = { page: "dashboard", video: videoSrc };
// A ref to apply to your video element. Cord will
// use this ref when users create annotations, applying
// the `location` object to the annotation.
const videoElementRef = useCordAnnotationTargetRef(location);
// The callback provided here will be used to enrich the
// `location` data for the annotation. You would use
// this hook when you want to put more specific location
// information in the annotation. Here, for instance,
// we're adding the current time of the video player
// as extra `location` data.
useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
return {
extraLocation: {
time: videoElementRef.current?.currentTime ?? 0,
},
// The `label` property is a special field. Setting
// this value will change how the annotation is displayed
// within Cord. Instead of showing the word "Annotation"
// in Cord, the label provided here will be used. You
// can use this to make annotation easier for your users
// to understand and navigate.
label: "Video: " + timestampToString(time),
};
});
return <video ref={videoElementRef} src={videoSrc} />;
}
You can control the exact annotation pin position on the page through the useCordAnnotationRenderer
hook. The function you provide will be called any time the pin positions on the page are refreshed. The function receives theAnnotation
object as an argument. You can return either absolute document coordinates, or coordinates relative to a DOM element.
import {
useCordAnnotationTargetRef,
useCordAnnotationCaptureHandler,
} from "@cord-sdk/react";
type VideoPlayerAnnotation = {
page: string;
video: string;
time: number;
};
function VideoPlayerComponent({ videoSrc }) {
// The initial location information for any annotations
// users create.
const location = { page: "dashboard", video: videoSrc };
// A ref to apply to your video element. Cord will
// use this ref when users create annotations, applying
// the `location` object to the annotation.
const videoElementRef = useCordAnnotationTargetRef(location);
// The callback provided here will be used to enrich the
// `location` data for the annotation. You would use
// this hook when you want to put more specific location
// information in the annotation. Here, for instance,
// we're adding the current time of the video player
// as extra `location` data.
useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
return {
extraLocation: {
time: videoElementRef.current?.currentTime ?? 0,
},
// The `label` property is a special field. Setting
// this value will change how the annotation is displayed
// within Cord. Instead of showing the word "Annotation"
// in Cord, the label provided here will be used. You
// can use this to make annotation easier for your users
// to understand and navigate.
label: "Video: " + timestampToString(time),
};
});
// The callback provided to this hook will receive the location
// object associated with the annotation. This is the same
// object that we constructed in the above code where we
// combined the `location` object with the `extraLocation`
// field in the `useCordAnnotationCaptureHandler` hook.
useCordAnnotationRenderer(location, (annotationLocation: VideoPlayerAnnotation) => {
if (!videoElementRef.current) {
// if the video element is for some reason not rendered yet,
// don't show the annotation pin
return;
}
// If the video player's current time isn't close to the
// timestamp where the annotation was created, don't show
// the annotation.
if (
annotationLocation.time < videoElementRef.current.currentTime - 2.5 &&
annotationLocation.time > videoElementRef.current.currentTime + 2.5
) {
return;
}
// If we made it here, we know that the annotation makes sense to show, so
// we'll put in on the video player in the bottom right corner.
return {
coordinates: { x: "90%", y: "90%" },
element: videoElementRef.current,
};
});
return <video ref={videoElementRef} src={videoSrc} />;
}
By default, the entire page can be annotated, which means the user can leave annotations on parts of the page that are irrelevant, like side navigation, top header, etc. You can control which parts of your page the user can place annotations on using thedata-cord-annotation-allowed
attribute.
By adding data-cord-annotation-allowed=false
on an element you can disable the annotation feature on that element and its entire DOM subtree.
<body>
<div id="sidebar" data-cord-annotation-allowed="false">
{/* Cord annotations are not allowed here */
</div>
<div id="content">
{/*
Cord annotations are allowed here because
they are allowed by default
*/}
</div>
</body>
Another use case is to disallow annotations on the entire document except for specific elements. You can do that by adding data-cord-annotation-allowed=false
on a top element like <body> and then selectively enabling it on specific elements, with data-cord-annotation-allowed=true
. In this case, you might want to screenshot only these elements. To do so, you can use the ScreenshotConfig API.
<body data-cord-annotation-allowed="false">
{/* The line above disables annotations everywhere */}
<div id="sidebar" />
<div id="content" data-cord-annotation-allowed="true">
{/*
Cord annotations are allowed only on content
within this <div> because it has the
`data-cord-annotation-allowed` attribute
explicitly set.
*/}
</div>
</body>
When the user clicks an annotation in the sidebar or in a thread, you might need to change your app's state to show that annotation properly. For example, you may need to skip a video to the annotated timestamp, or fetch data for a chart within a specific date interval.
Through the useCordAnnotationClickHandler hook you can provide a callback that will be called with theAnnotationWithThreadID
object when the user clicks on the annotation pill in the message. You're not expected to return anything from the callback; it's just an event.
import {
useCordAnnotationTargetRef,
useCordAnnotationCaptureHandler,
} from "@cord-sdk/react";
type VideoPlayerAnnotation = {
page: string;
video: string;
time: number;
};
function VideoPlayerComponent({ videoSrc }) {
// The initial location information for any annotations
// users create.
const location = { page: "dashboard", video: videoSrc };
// A ref to apply to your video element. Cord will
// use this ref when users create annotations, applying
// the `location` object to the annotation.
const videoElementRef = useCordAnnotationTargetRef(location);
// The callback provided here will be used to enrich the
// `location` data for the annotation. You would use
// this hook when you want to put more specific location
// information in the annotation. Here, for instance,
// we're adding the current time of the video player
// as extra `location` data.
useCordAnnotationCaptureHandler<VideoPlayerAnnotation>(location, () => {
return {
extraLocation: {
time: videoElementRef.current?.currentTime ?? 0,
},
// The `label` property is a special field. Setting
// this value will change how the annotation is displayed
// within Cord. Instead of showing the word "Annotation"
// in Cord, the label provided here will be used. You
// can use this to make annotation easier for your users
// to understand and navigate.
label: "Video: " + timestampToString(time),
};
});
// The callback provided to this hook will receive the location
// object associated with the annotation. This is the same
// object that we constructed in the above code where we
// combined the `location` object with the `extraLocation`
// field in the `useCordAnnotationCaptureHandler` hook.
useCordAnnotationRenderer(location, (annotationLocation: VideoPlayerAnnotation) => {
if (!videoElementRef.current) {
// if the video element is for some reason not rendered yet,
// don't show the annotation pin
return;
}
// If the video player's current time isn't close to the
// timestamp where the annotation was created, don't show
// the annotation.
if (
annotationLocation.time < videoElementRef.current.currentTime - 2.5 &&
annotationLocation.time > videoElementRef.current.currentTime + 2.5
) {
return;
}
// This hook allows you to know when the user has clicked on a particular
// annotation. You might use this to jump to a particular point in a video,
// the scroll the page to a particular position, or otherwise change around
// the UI to highlight what the user has shown interest in.
useCordAnnotationClickHandler<VideoPlayerAnnotation>(
location,
(annotation) => {
if (videoElementRef.current) {
// Skip the video to the annotation timestamp and pause
videoElementRef.current.currentTime = annotation.location.time;
videoElementRef.current.pause();
}
}
);
// If we made it here, we know that the annotation makes sense to show, so
// we'll put in on the video player in the bottom right corner.
return {
coordinates: { x: "90%", y: "90%" },
element: videoElementRef.current,
};
});
return <video ref={videoElementRef} src={videoSrc} />;
}
Your app now supports rich, precise annotations.