Skip to content

Instantly share code, notes, and snippets.

@kadamwhite
Last active March 31, 2026 17:30
Show Gist options
  • Select an option

  • Save kadamwhite/03e6b12cf4e0e416983cce7995537307 to your computer and use it in GitHub Desktop.

Select an option

Save kadamwhite/03e6b12cf4e0e416983cce7995537307 to your computer and use it in GitHub Desktop.
Bookmarklet: Annotate or circle things on the webpage
javascript:(function()%7B%2F**%0A%20*%20Annotate%20a%20page%20with%20writeable%20labels.%0A%20*%0A%20*%20Script%20variant%20that%20doesn't%20reposition%20the%20circle%20(which%20is%20a%20rounded%0A%20*%20rectangle%20this%20time)%20when%20you%20release%20the%20mouse.%0A%20*%0A%20*%20This%20version%20(copy%20the%20bookmarklet%20code%20from%20the%20file%20above)%20will%20add%0A%20*%20a%20label%20field%20to%20each%20annotation%2C%20and%20you%20can%20right-click%20an%20annotation%0A%20*%20to%20remove%20it.%0A%20*%2F%0A(()%20%3D%3E%20%7B%0A%20%20%20%20const%20id%20%3D%20'bookmarklet-annotate'%3B%0A%20%20%20%20const%20blanket%20%3D%20document.getElementById(%20id%20)%20%7C%7C%20document.createElement('div')%3B%0A%20%20%20%20if%20(%20blanket.id%20%3D%3D%3D%20id%20)%20%7B%0A%20%20%20%20%20%20%20%20%2F%2F%20Already%20in%20annotate%20mode%3A%20Remove%20blanket%20to%20exit%20annotate%20mode.%0A%20%20%20%20%20%20%20%20blanket.remove()%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%20%20%20%20%2F%2F%20Otherwise%2C%20set%20up%20the%20interface.%0A%20%20%20%20blanket.id%20%3D%20id%3B%0A%20%20%20%20blanket.style.zIndex%20%3D%201000%3B%0A%20%20%20%20blanket.style.position%20%3D%20'absolute'%3B%0A%20%20%20%20blanket.style.top%20%3D%200%3B%0A%20%20%20%20blanket.style.width%20%3D%20'100vw'%3B%0A%20%20%20%20blanket.style.height%20%3D%20%60%24%7B%20document.body.scrollHeight%20%7Dpx%60%3B%0A%0A%20%20%20%20const%20logEvent%20%3D%20(%20evt%20)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20partialEvent%20%3D%20%7B%7D%3B%0A%20%20%20%20%20%20%20%20for%20(%20const%20key%20in%20evt%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(%20key.match(%20%2F(X%7CY)%24%2F%20)%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20partialEvent%5B%20key%20%5D%20%3D%20evt%5B%20key%20%5D%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20console.log(%20partialEvent%20)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20function%20makeOutline(%20%7B%20x%3A%20startX%2C%20y%3A%20startY%20%7D%20)%20%7B%0A%20%20%20%20%20%20%20%20const%20outline%20%3D%20document.createElement('div')%3B%0A%20%20%20%20%20%20%20%20outline.style.position%20%3D%20'absolute'%3B%0A%20%20%20%20%20%20%20%20outline.style.display%20%3D%20'none'%3B%0A%20%20%20%20%20%20%20%20outline.style.border%20%3D%20'7px%20dotted%20hotpink'%3B%0A%20%20%20%20%20%20%20%20outline.style.borderRadius%20%3D%20'1rem'%3B%0A%0A%20%20%20%20%20%20%20%20outline.style.display%20%3D%20'block'%3B%0A%20%20%20%20%20%20%20%20outline.style.height%20%3D%200%3B%0A%20%20%20%20%20%20%20%20outline.style.left%20%3D%20%60%24%7BstartX%7Dpx%60%3B%0A%20%20%20%20%20%20%20%20outline.style.top%20%3D%20%60%24%7BstartY%7Dpx%60%3B%0A%20%20%20%20%20%20%20%20outline.style.width%20%3D%200%3B%0A%0A%20%20%20%20%20%20%20%20const%20label%20%3D%20document.createElement(%20'DIV'%20)%3B%0A%20%20%20%20%20%20%20%20label.classList.add(%20'annotation-label'%20)%3B%0A%20%20%20%20%20%20%20%20label.contentEditable%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20label.style.background%20%3D%20'rgba(255%2C255%2C255%2C0.9)'%3B%0A%20%20%20%20%20%20%20%20label.style.transform%20%3D%20'translateY(-60%25)'%3B%0A%20%20%20%20%20%20%20%20label.style.color%20%3D%20'hotpink'%3B%0A%20%20%20%20%20%20%20%20label.style.display%20%3D%20'inline-block'%3B%0A%20%20%20%20%20%20%20%20label.style.minWidth%20%3D%20'100px'%3B%0A%20%20%20%20%20%20%20%20label.style.padding%20%3D%20'0.25rem%200.5rem'%3B%0A%20%20%20%20%20%20%20%20label.style.marginLeft%20%3D%20'0.75rem'%3B%0A%20%20%20%20%20%20%20%20label.style.fontWeight%20%3D%20800%3B%0A%20%20%20%20%20%20%20%20label.style.fontSize%20%3D%20'24px'%3B%0A%20%20%20%20%20%20%20%20label.style.whiteSpace%20%3D%20'nowrap'%3B%0A%20%20%20%20%20%20%20%20outline.appendChild(%20label%20)%3B%0A%0A%0A%20%20%20%20%20%20%20%20label.addEventListener('pointerdown'%2C%20(%20evt%20)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Allow%20selecting%20label%20without%20passing%20up%20the%20chain.%0A%20%20%20%20%20%20%20%20%20%20%20%20evt.stopPropagation()%3B%0A%20%20%20%20%20%20%20%20%7D%20)%3B%0A%0A%20%20%20%20%20%20%20%20const%20removeOnRightClick%20%3D%20(%20evt%20)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20evt.preventDefault()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20outline.remove()%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20outline.addEventListener(%20'contextmenu'%2C%20removeOnRightClick%20)%3B%0A%0A%20%20%20%20%20%20%20%20const%20updateOutline%20%3D%20(%20evt%20)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20outline.style.display%20%3D%20'block'%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20outline.style.top%20%3D%20%60%24%7B%20startY%20%3C%20evt.pageY%20%3F%20startY%20%3A%20evt.pageY%7Dpx%60%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20outline.style.left%20%3D%20%60%24%7B%20startX%20%3C%20evt.pageX%20%3F%20startX%20%3A%20evt.pageX%7Dpx%60%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20outline.style.height%20%3D%20%60%24%7BMath.round(%20Math.abs(%20evt.pageY%20-%20startY%20)%20)%7Dpx%60%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20outline.style.width%20%3D%20%60%24%7BMath.round(%20Math.abs(%20evt.pageX%20-%20startX%20)%20)%7Dpx%60%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%20%20%20%20const%20finishOutline%20%3D%20(%20evt%20)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20blanket.removeEventListener(%20'pointermove'%2C%20updateOutline%20)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20blanket.removeEventListener(%20'pointerup'%2C%20finishOutline%20)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20outline.querySelector(%20'.annotation-label'%20)%3F.focus()%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%0A%20%20%20%20%20%20%20%20const%20finishLabel%20%3D%20(%20evt%20)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20label.style.minWidth%20%3D%200%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20label.removeEventListener(%20'blur'%2C%20finishLabel%20)%3B%0A%20%20%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%20%20%20%20label.addEventListener(%20'blur'%2C%20finishLabel%20)%3B%0A%0A%20%20%20%20%20%20%20%20blanket.appendChild(%20outline%20)%3B%0A%20%20%20%20%20%20%20%20blanket.addEventListener(%20'pointermove'%2C%20updateOutline%20)%3B%0A%20%20%20%20%20%20%20%20blanket.addEventListener(%20'pointerup'%2C%20finishOutline%20)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20document.body.appendChild(blanket)%3B%0A%0A%20%20%20%20blanket.addEventListener('pointerdown'%2C%20(%20evt%20)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20if%20(%20evt.button%20%3D%3D%3D%202%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Right%20click%3A%20take%20no%20action.%0A%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20makeOutline(%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20x%3A%20evt.pageX%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20y%3A%20evt.pageY%2C%0A%20%20%20%20%20%20%20%20%7D%20)%3B%0A%20%20%20%20%7D%20)%3B%0A%7D)()%3B%7D)()%3B
/**
* Annotate a page with writeable labels.
*
* Script variant that doesn't reposition the circle (which is a rounded
* rectangle this time) when you release the mouse.
*
* This version (copy the bookmarklet code from the file above) will add
* a label field to each annotation, and you can right-click an annotation
* to remove it.
*/
(() => {
const id = 'bookmarklet-annotate';
const blanket = document.getElementById( id ) || document.createElement('div');
if ( blanket.id === id ) {
// Already in annotate mode: Remove blanket to exit annotate mode.
blanket.remove();
return;
}
// Otherwise, set up the interface.
blanket.id = id;
blanket.style.zIndex = 1000;
blanket.style.position = 'absolute';
blanket.style.top = 0;
blanket.style.width = '100vw';
blanket.style.height = `${ document.body.scrollHeight }px`;
const logEvent = ( evt ) => {
const partialEvent = {};
for ( const key in evt ) {
if ( key.match( /(X|Y)$/ ) ) {
partialEvent[ key ] = evt[ key ];
}
}
console.log( partialEvent );
}
function makeOutline( { x: startX, y: startY } ) {
const outline = document.createElement('div');
outline.style.position = 'absolute';
outline.style.display = 'none';
outline.style.border = '7px dotted hotpink';
outline.style.borderRadius = '1rem';
outline.style.display = 'block';
outline.style.height = 0;
outline.style.left = `${startX}px`;
outline.style.top = `${startY}px`;
outline.style.width = 0;
const label = document.createElement( 'DIV' );
label.classList.add( 'annotation-label' );
label.contentEditable = true;
label.style.background = 'rgba(255,255,255,0.9)';
label.style.transform = 'translateY(-60%)';
label.style.color = 'hotpink';
label.style.display = 'inline-block';
label.style.minWidth = '100px';
label.style.padding = '0.25rem 0.5rem';
label.style.marginLeft = '0.75rem';
label.style.fontWeight = 800;
label.style.fontSize = '24px';
label.style.whiteSpace = 'nowrap';
outline.appendChild( label );
label.addEventListener('pointerdown', ( evt ) => {
// Allow selecting label without passing up the chain.
evt.stopPropagation();
} );
const removeOnRightClick = ( evt ) => {
evt.preventDefault();
outline.remove();
};
outline.addEventListener( 'contextmenu', removeOnRightClick );
const updateOutline = ( evt ) => {
outline.style.display = 'block';
outline.style.top = `${ startY < evt.pageY ? startY : evt.pageY}px`;
outline.style.left = `${ startX < evt.pageX ? startX : evt.pageX}px`;
outline.style.height = `${Math.round( Math.abs( evt.pageY - startY ) )}px`;
outline.style.width = `${Math.round( Math.abs( evt.pageX - startX ) )}px`;
};
const finishOutline = ( evt ) => {
blanket.removeEventListener( 'pointermove', updateOutline );
blanket.removeEventListener( 'pointerup', finishOutline );
outline.querySelector( '.annotation-label' )?.focus();
};
const finishLabel = ( evt ) => {
label.style.minWidth = 0;
label.removeEventListener( 'blur', finishLabel );
};
label.addEventListener( 'blur', finishLabel );
blanket.appendChild( outline );
blanket.addEventListener( 'pointermove', updateOutline );
blanket.addEventListener( 'pointerup', finishOutline );
}
document.body.appendChild(blanket);
blanket.addEventListener('pointerdown', ( evt ) => {
if ( evt.button === 2 ) {
// Right click: take no action.
return;
}
makeOutline( {
x: evt.pageX,
y: evt.pageY,
} );
} );
})();
javascript:(function()%7B(()%3D>%7Blet e%3Ddocument.createElement("div")%3Be.style.zIndex%3D1e3%2Ce.style.position%3D"absolute"%2Ce.style.top%3D0%2Ce.style.width%3D"100vw"%2Ce.style.height%3D%60%24%7Bdocument.body.scrollHeight%7Dpx%60%3Blet t%2Cl%3Ddocument.createElement("div")%3Bl.style.position%3D"absolute"%2Cl.style.display%3D"none"%2Cl.style.border%3D"5px dotted hotpink"%2Cl.style.borderRadius%3D"45%25"%2Cl.style.pointerEvents%3D"none"%3Blet s%3De%3D>%7Bl.style.display%3D"block"%2Cl.style.top%3D%60%24%7Bt.y<e.pageY%3Ft.y%3Ae.pageY%7Dpx%60%2Cl.style.left%3D%60%24%7Bt.x<e.pageX%3Ft.x%3Ae.pageX%7Dpx%60%2Cl.style.height%3D%60%24%7BMath.round(Math.abs(e.pageY-t.y))%7Dpx%60%2Cl.style.width%3D%60%24%7BMath.round(Math.abs(e.pageX-t.x))%7Dpx%60%7D%3Be.addEventListener("pointerdown"%2Cp%3D>%7Bt%3D%7Bx%3Ap.pageX%2Cy%3Ap.pageY%7D%2Cl.style.display%3D"block"%2Cl.style.height%3D0%2Cl.style.left%3D%60%24%7Bt.x%7Dpx%60%2Cl.style.top%3D%60%24%7Bt.y%7Dpx%60%2Cl.style.width%3D0%2Ce.addEventListener("pointermove"%2Cs)%7D)%2Ce.addEventListener("pointerup"%2Ct%3D>%7Be.removeEventListener("pointermove"%2Cs)%7D)%2Cdocument.body.appendChild(e)%2Ce.appendChild(l)%7D)()%7D)()
(() => {
const blanket = document.createElement('div');
blanket.style.zIndex = 1000;
blanket.style.position = 'absolute';
blanket.style.top = 0;
blanket.style.width = '100vw';
blanket.style.height = `${ document.body.scrollHeight }px`;
let startCoords;
const outline = document.createElement('div');
outline.style.position = 'absolute';
outline.style.display = 'none';
outline.style.border = '5px dotted hotpink';
outline.style.borderRadius = '45%';
outline.style.pointerEvents = 'none';
const logEvent = ( evt ) => {
const partialEvent = {};
for ( const key in evt ) {
if ( key.match( /(X|Y)$/ ) ) {
partialEvent[ key ] = evt[ key ];
}
}
console.log( partialEvent );
}
const updateOutline = ( evt ) => {
outline.style.display = 'block';
outline.style.top = `${ startCoords.y < evt.pageY ? startCoords.y : evt.pageY}px`;
outline.style.left = `${ startCoords.x < evt.pageX ? startCoords.x : evt.pageX}px`;
outline.style.height = `${Math.round( Math.abs( evt.pageY - startCoords.y ) )}px`;
outline.style.width = `${Math.round( Math.abs( evt.pageX - startCoords.x ) )}px`;
};
blanket.addEventListener('pointerdown', ( evt ) => {
startCoords = {
x: evt.pageX,
y: evt.pageY,
};
outline.style.display = 'block';
outline.style.height = 0;
outline.style.left = `${startCoords.x}px`;
outline.style.top = `${startCoords.y}px`;
outline.style.width = 0;
blanket.addEventListener( 'pointermove', updateOutline );
} );
blanket.addEventListener( 'pointerup', ( evt ) => {
blanket.removeEventListener( 'pointermove', updateOutline );
} );
document.body.appendChild(blanket);
blanket.appendChild( outline );
})();
(()=>{let e=document.createElement("div");e.style.zIndex=1e3,e.style.position="absolute",e.style.top=0,e.style.width="100vw",e.style.height=`${document.body.scrollHeight}px`;let t,l=document.createElement("div");l.style.position="absolute",l.style.display="none",l.style.border="5px dotted hotpink",l.style.borderRadius="45%",l.style.pointerEvents="none";let s=e=>{l.style.display="block",l.style.top=`${t.y<e.pageY?t.y:e.pageY}px`,l.style.left=`${t.x<e.pageX?t.x:e.pageX}px`,l.style.height=`${Math.round(Math.abs(e.pageY-t.y))}px`,l.style.width=`${Math.round(Math.abs(e.pageX-t.x))}px`};e.addEventListener("pointerdown",p=>{t={x:p.pageX,y:p.pageY},l.style.display="block",l.style.height=0,l.style.left=`${t.x}px`,l.style.top=`${t.y}px`,l.style.width=0,e.addEventListener("pointermove",s)}),e.addEventListener("pointerup",t=>{e.removeEventListener("pointermove",s)}),document.body.appendChild(e),e.appendChild(l)})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment