Skip to content

Instantly share code, notes, and snippets.

@loonkwil
Last active September 8, 2023 18:44
Show Gist options
  • Save loonkwil/c3be79343c323998c8bedc0a9206e670 to your computer and use it in GitHub Desktop.
Save loonkwil/c3be79343c323998c8bedc0a9206e670 to your computer and use it in GitHub Desktop.
πŸ“ Bookmarklet to measure any element on a webpage

Create a new bookmark with the following URL (or execute it in a Dev Console):

javascript:{/*! v1.1 */const x=new AbortController,d=document,a=(e,s,o=d.body)=>o.addEventListener(e,s,{signal:x.signal}),p=(e,s={},o=[])=>{const r=d.createElement(e);return Object.entries(s).map(([i,g])=>r[i]=g),r.append(...o),r},f=p("div",{style:"all:revert;height:10px;align-self:start;background:no-repeat 50%/100% 2px linear-gradient(#000,#000 1px,#fff 0,#fff 2px,transparent 0);border-color:#000;border-width:0 1px 0;border-style:solid;cursor:ew-resize;user-select:none;"}),y=p("div",{style:"all:revert;padding:1px 2px;align-self:center;border-radius:2px;font:700 12px system-ui;color:#fff;background:#000;cursor:move;user-select:none;"}),n=p("div",{tabIndex:0,style:"all:revert;position:absolute;display:inline-flex;flex-direction:column;gap:1px;outline-offset:1px;"},[f,y]),u={x:e=>n.style.left=e+"px",y:e=>n.style.top=e+"px",s:e=>y.textContent=f.style.width=e+"px",f:e=>e?(n.style.zIndex=1e9,n.style.opacity=1):(n.style.zIndex=1e8,n.style.opacity=.4)},t=new Proxy({},{set(e,s,o){u[s](o),e[s]=o}});d.body.append(n),a("focus",()=>t.f=!0,n),a("blur",()=>t.f=!1,n);let c,l={};a("keydown",e=>{const{key:s,repeat:o}=e;if(["Backspace","Escape"].includes(s)){x.abort(),n.remove();return}if(s==="s"){e.preventDefault(),console.log(`Ruler's position: [${t.x}, ${t.y}], size: ${t.s}px`);return}const r={"+":[1,0],"-":[-1,0],ArrowUp:[0,-1],ArrowLeft:[-1,0],ArrowDown:[0,1],ArrowRight:[1,0]}[s];if(r){e.preventDefault();const i=o?5:1;s.startsWith("Arrow")?(t.x+=r[0]*i,t.y+=r[1]*i):t.s+=r[0]*i}},n),a("mousedown",({target:e,clientX:s,clientY:o})=>{c=e===f?"r":"m",l.s={...t},l.c={x:s,y:o}},n),a("mouseup",()=>c=void 0),a("mousemove",({buttons:e,clientX:s,clientY:o})=>{if(e&1&&c){const i={x:s-l.c.x,y:o-l.c.y};c==="m"?(t.x=l.s.x+i.x,t.y=l.s.y+i.y):t.s=l.s.s+i.x}}),t.x=t.y=t.s=100,n.focus()}

Screenshot showing what the program looks like in action

Keyboard shortcuts:

  • Backspace or Escape: Remove ruler
  • +/-: Increase/decrease the size of the ruler
  • Arrow keys: Change the position of the ruler
  • s: Write the position and the size of the ruler to the console
javascript: {
/*! v1.1 */
/** @typedef {{ x: number, y: number, size: number, focus: boolean }} State */
const controller = new AbortController();
/**
* @param {string} event
* @param {function(Event)} cb
* @param {EventTarget=} node
*/
const on = (event, cb, node = document.body) =>
node.addEventListener(event, cb, {
signal: controller.signal,
});
/**
* @param {string} type
* @param {Object=} props
* @param {Array<Node | string>=} children
* @returns {Element}
* @example
* // returns <div id="A">B</div>
* h("div", { id: "A" }, ["B"]);
*/
const h = (type, props = {}, children = []) => {
const $el = document.createElement(type);
Object.entries(props).forEach(([key, value]) => ($el[key] = value));
$el.append(...children);
return $el;
};
const $line = h("div", {
style: `
all: revert;
height: 10px;
align-self: start;
background:
no-repeat
50%/100% 2px
linear-gradient(#000, #000 1px, #fff 1px, #fff 2px, transparent 2px);
border-color: #000;
border-width: 0 1px 0;
border-style: solid;
cursor: ew-resize;
user-select: none;
`,
});
const $info = h("div", {
style: `
all: revert;
padding: 1px 2px;
align-self: center;
border-radius: 2px;
font: bold 12px system-ui;
color: #fff;
background: #000;
cursor: move;
user-select: none;
`,
});
const $app = h(
"div",
{
tabIndex: 0,
style: `
all: revert;
position: absolute;
display: inline flex;
flex-direction: column;
gap: 1px;
outline-offset: 1px;
`,
},
[$line, $info],
);
const observers = {
x: (v) => ($app.style.left = v + "px"),
y: (v) => ($app.style.top = v + "px"),
size: (v) => ($info.textContent = $line.style.width = v + "px"),
focus: (v) =>
v
? (($app.style.zIndex = 1e9), ($app.style.opacity = 1))
: (($app.style.zIndex = 1e8), ($app.style.opacity = 0.4)),
};
/** @type {State} */
const state = new Proxy(
{},
{
set(obj, prop, value) {
observers[prop](value);
obj[prop] = value;
},
},
);
document.body.append($app);
on("focus", () => (state.focus = true), $app);
on("blur", () => (state.focus = false), $app);
/**
* @type {string|undefined} "resize", "move" or undefined (if the user does not drag any element)
*/
let action;
/**
* @type {{
* state?: State,
* cursor?: { x: number, y: number }
* }} The state of the app and the position of the cursor when the drag starts
*/
let start = {};
on(
"keydown",
(e) => {
const { key, repeat } = e;
if (["Backspace", "Escape"].includes(key)) {
controller.abort();
$app.remove();
return;
}
if (key === "s") {
e.preventDefault();
console.log(
`Ruler's position: [${state.x}, ${state.y}], size: ${state.size}px`,
);
return;
}
const dir = {
"+": [1, 0],
"-": [-1, 0],
ArrowUp: [0, -1],
ArrowLeft: [-1, 0],
ArrowDown: [0, 1],
ArrowRight: [1, 0],
}[key];
if (dir) {
e.preventDefault();
const amount = repeat ? 5 : 1;
if (key.startsWith("Arrow")) {
state.x += dir[0] * amount;
state.y += dir[1] * amount;
} else {
state.size += dir[0] * amount;
}
}
},
$app,
);
on(
"mousedown",
({ target, clientX, clientY }) => {
action = target === $line ? "resize" : "move";
start.state = { ...state };
start.cursor = { x: clientX, y: clientY };
},
$app,
);
on("mouseup", () => (action = undefined));
on("mousemove", ({ buttons, clientX, clientY }) => {
const dragging = buttons & 1;
if (dragging && action) {
const delta = {
x: clientX - start.cursor.x,
y: clientY - start.cursor.y,
};
switch (action) {
case "move":
state.x = start.state.x + delta.x;
state.y = start.state.y + delta.y;
break;
case "resize":
state.size = start.state.size + delta.x;
break;
}
}
});
state.x = 100;
state.y = 100;
state.size = 100;
$app.focus();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment