Skip to content

Instantly share code, notes, and snippets.

@mrclay
Created May 19, 2026 17:18
Show Gist options
  • Select an option

  • Save mrclay/5a8d434b41f8b2e80ec4129146d1a2ce to your computer and use it in GitHub Desktop.

Select an option

Save mrclay/5a8d434b41f8b2e80ec4129146d1a2ce to your computer and use it in GitHub Desktop.
React/web components to measure height of an element
(() => {
/**
* Measures the height of the previous element.
*
* This example will show the height of the P element, including any
* padding/borders:
*
* <p>...</p>
* <measure-height></measure-height>
*/
class MeasureHeightElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this._observer = null;
this._onResize = () => this.update();
}
connectedCallback() {
this.update();
window.addEventListener("resize", this._onResize);
const prevEl = this.previousElementSibling;
if (!prevEl) {
return;
}
this._observer = new ResizeObserver(() => this.update());
this._observer.observe(prevEl);
}
disconnectedCallback() {
window.removeEventListener("resize", this._onResize);
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
}
update() {
const prevEl = this.previousElementSibling;
if (!prevEl) {
return;
}
const height = prevEl.getBoundingClientRect().height;
const [whole, decimal] = String(Math.round(height * 1000) / 1000).split(
".",
);
const rest = decimal ? `.${decimal}` : "";
const style = `
:host {
background-color: #fff;
background-image: linear-gradient(to left, #ccc, #ccc);
background-size: 1px 100%;
background-position: center;
background-repeat: no-repeat;
border-block: 1px solid #ccc;
border-inline: 5px solid transparent;
color: #000;
display: block;
font: 14px/17px system-ui, sans-serif;
position: absolute;
width: 20px;
}
span[a] {
position: absolute;
inset: calc(50% - 10px) -5rem calc(50% - 10px) -5rem;
line-height: 20px;
padding: 0 4px;
text-align: center;
transform: rotate(90deg);
}
span[b] {
background: #fff;
}
span[c] {
opacity: 0.5;
}
`;
this.shadowRoot.innerHTML = `
<style>${style}</style>
<span a><span b>${whole}<span c>${rest}</span></span></span>
`;
// Absolutely position the element to the top right of the previous sibling
this.style.top = `${prevEl.offsetTop}px`;
this.style.left = `${prevEl.offsetLeft + prevEl.offsetWidth}px`;
this.style.height = `${prevEl.offsetHeight}px`;
}
}
if (!customElements.get("measure-height")) {
customElements.define("measure-height", MeasureHeightElement);
}
})();
import React, { useEffect, useRef, useState } from "react";
/**
* Measures the height of the preceding sibling element.
*
* This example will show the height of the P element, including any
* padding/borders:
*
* <p>...</p>
* <MeasureHeight />
*/
function MeasureHeight() {
const [height, setHeight] = useState<number>(0);
const [position, setPosition] = useState({ top: 0, left: 0 });
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = containerRef.current;
if (!el) {
return;
}
const prevEl = el.previousElementSibling as HTMLElement;
if (!prevEl) {
return;
}
const update = () => {
const rect = prevEl.getBoundingClientRect();
setHeight(rect.height);
setPosition({
top: prevEl.offsetTop,
left: prevEl.offsetLeft + prevEl.offsetWidth,
});
};
// Initial update
update();
const observer = new ResizeObserver(update);
observer.observe(prevEl);
window.addEventListener("resize", update);
return () => {
observer.disconnect();
window.removeEventListener("resize", update);
};
}, []);
const whole = Math.floor(height);
// e.g., ".123"
const rest = (height % 1).toFixed(3).substring(1);
const styles: Record<string, React.CSSProperties> = {
container: {
position: "absolute",
top: `${position.top}px`,
left: `${position.left}px`,
height: `${height}px`,
width: "20px",
backgroundColor: "#fff",
backgroundImage: "linear-gradient(to left, #ccc, #ccc)",
backgroundSize: "1px 100%",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
borderBlock: "1px solid #ccc",
borderInline: "5px solid transparent",
color: "#000",
font: "14px/17px system-ui, sans-serif",
pointerEvents: "none", // Prevents interaction blocking
},
spanA: {
position: "absolute",
inset: "calc(50% - 10px) -5rem calc(50% - 10px) -5rem",
lineHeight: "20px",
padding: "0 4px",
textAlign: "center",
transform: "rotate(90deg)",
},
spanB: {
background: "#fff",
},
spanC: {
opacity: 0.5,
},
};
return (
<div ref={containerRef} style={styles.container}>
<span style={styles.spanA}>
<span style={styles.spanB}>
{whole}
<span style={styles.spanC}>{rest}</span>
</span>
</span>
</div>
);
}
export default MeasureHeight;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment