// Code adapted from `xywh.js` by Thomas Steiner // https://github.com/tomayac/xywh.js export type MediaFragment = ( | { mediaItem: HTMLImageElement; mediaType: "img"; } | { mediaItem: HTMLVideoElement; mediaType: "video"; } ) & { unit: string; x: number; y: number; w: number; h: number; }; export function xywh(mediaItem: HTMLImageElement | HTMLVideoElement) { const source = mediaItem.src || mediaItem.currentSrc; // See http://www.w3.org/TR/media-frags/#naming-space const xywhRegEx = /[#&\?]xywh\=(pixel\:|percent\:)?([\d\.]+),([\d\.]+),([\d\.]+),([\d\.]+)/; const match = xywhRegEx.exec(source); if (match) { const mediaFragment = { mediaItem: mediaItem, mediaType: mediaItem.nodeName.toLowerCase(), unit: match[1] ? match[1] : "pixel:", x: parseFloat(match[2]), y: parseFloat(match[3]), w: parseFloat(match[4]), h: parseFloat(match[5]), } as MediaFragment; if (mediaFragment.mediaType === "img") { addImageLoadListener(mediaFragment); } else { addVideoLoadListener(mediaFragment); } } } /** * Applies the media fragment when the image has loaded. We need the image's * original width and height. */ function addImageLoadListener(mediaFragment: MediaFragment) { const mediaItem = mediaFragment.mediaItem; const onload = function () { applyFragment(mediaFragment); // Removes the load listener from the image, so that it doesn't fire // again when we set the image's @src to a transparent 1x1 GIF, but only // once when the initial image has fully loaded mediaItem.removeEventListener("load", onload); // Base64-encoded transparent 1x1 pixel GIF mediaItem.src = "data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="; }; mediaItem.addEventListener("load", onload); } /** * Applies the media fragment when all metadata of the video have loaded. We * need the video's original width and height. */ function addVideoLoadListener(mediaFragment: MediaFragment) { mediaFragment.mediaItem.addEventListener("loadedmetadata", function () { applyFragment(mediaFragment); }); } /** * Applies the spatial media fragment: * * For images, we replace the @src attribute with a transparent GIF data URL, * resize the image to match the fragment's dimensions, and set the image's * CSS background-image according to the fragment's x and y values. * * For videos, we wrap the video in a