ul
li(data-js='detect-wrap')
a(href='https://dev.to/rossangus/detecting-how-many-times-some-text-wraps-with-javascript-1p35') Read the full writeup
li(data-js='detect-wrap') Vivamus at felis quis est maximus porttitor eu eget lectus
li(data-js='detect-wrap') Sed ipsum enim, euismod dictum justo non, placerat tincidunt risus. Duis feugiat in mi non pulvinar. Proin lorem enim, ornare sed rutrum in, facilisis eget sem. Donec vitae ex tincidunt, efficitur ex iaculis, gravida arcu
li(data-js='detect-wrap') Pellentesque ultricies rhoncus arcu. Aliquam non ipsum neque. Pellentesque ante turpis, accumsan a feugiat id, euismod eget nisi. Integer lobortis, velit ut laoreet vestibulum, justo lorem varius dui, ut sagittis mauris ligula sed leo. Praesent eu metus dui. Praesent semper urna metus, nec consectetur quam efficitur vel
li(data-js='detect-wrap') Aliquam erat volutpat. Quisque venenatis tellus vel varius laoreet. Phasellus accumsan faucibus enim, ut cursus metus viverra sit amet. Ut venenatis condimentum interdum. Curabitur et turpis at augue sollicitudin commodo in quis mi. Pellentesque fringilla blandit pellentesque. Morbi quis sem in neque elementum rutrum. Curabitur et urna et purus posuere ornare. Nullam tincidunt ut lacus non posuere. Vestibulum vulputate sit amet purus ac laoreet. Aenean ac arcu non eros gravida pharetra quis et sem. Donec vitae metus ut ipsum rutrum vulputate et et orci. In dictum vulputate consectetur. Quisque interdum finibus auctor. Ut congue nulla sagittis tellus sollicitudin, vel commodo augue rutrum.
View Compiled
$boring-grey: rgb(128 128 128 / 0.5);
// Just some basic boilerplate. None of this CSS is required, for this to work
:root {
--dark: #111;
--light: #eee;
--warning: rgb(255 0 0 / 0.2);
}
body {
background: var(--light);
color: var(--dark);
}
@media (prefers-color-scheme: dark) {
body {
background: var(--dark);
color: var(--light);
}
a {
color: #99f;
&:visited {
color: #f9f;
}
}
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
// Simple grid layout to force wrapping
ul {
align-items: flex-start;
display: flex;
gap: 2em;
padding: 0;
}
// Messing with the vertical box model to show that the maths works
li {
border-bottom: solid 1em transparent;
border-top: solid 1em transparent;
flex: 1;
list-style: none;
margin: 1em 0;
// Added just in case it influenced the box model height (it doesn't)
outline: solid 0.2em transparent;
padding: 3em 0 1em;
position: relative;
&::before,
&::after {
left: 50%;
position: absolute;
transform: translate(-50%, 0);
z-index: 1;
}
// The wee bubble showing how many times the text wraps
&::before {
background: $boring-grey;
// Can't think of an elegant way to stop the `s` appearing if the text
// is just on one line
content: 'Wrapping ' attr(data-wrap) ' times';
font-size: 0.8em;
padding: 0.5em;
text-align: center;
bottom: 100%;
}
// The speechmark tick
&::after {
border: solid 1em transparent;
border-top-color: $boring-grey;
border-bottom-width: 0;
content: "";
bottom: calc(100% - 1em);
}
}
View Compiled
// Pinched from here: https://amitd.co/code/typescript/debounce
const Debounce = (callback, timeout) => {
let timer;
return (args) => {
clearTimeout(timer);
timer = setTimeout(() => {
callback(args);
}, timeout);
};
};
// This is passed an element and returns an approximation of how many times
// the current text inside the element wraps
// Assumption: the element passed doesn't feature a mix of content, for example
// h1, ul, li, p. If this is done, it will return how many times plane text
// will wrap inside the element, rather than the original mix of block elements
const lineNumbers = (element) => {
const elementStyles = window.getComputedStyle(element);
// As clientHeight contained the padding top and bottom values, we need
// to extract this information to remove it from the calculation
const paddingTop = parseInt(elementStyles.getPropertyValue("padding-top"), 10);
const paddingBottom = parseInt(elementStyles.getPropertyValue("padding-bottom"), 10);
// Let's save this for later
const currentContent = element.innerHTML;
const contentHeight = element.clientHeight - paddingTop - paddingBottom;
// Replace the content (temporarily!) with something which won't wrap
element.innerHTML = "i";
// Measure the height and store it
const lineHeight = element.clientHeight - paddingTop - paddingBottom;
// Put that content right back
element.innerHTML = currentContent;
// Approximation of how many times the line is wrapping
return Math.round(contentHeight / lineHeight);
};
// Let's not stress the browser any more than is necessary
const checkForWrap = Debounce((elements) => {
elements.forEach((element) => {
element.setAttribute("data-wrap", lineNumbers(element));
});
}, 100);
const DetectWrap = () => {
window.addEventListener("resize", () => {
// You could pass any selector you like
checkForWrap(document.querySelectorAll('[data-js="detect-wrap"]'));
});
// Rather than calling checkForWrap() a second time, dispatch a resize event
// to trigger it naturally
window.dispatchEvent(new Event("resize"));
};
DetectWrap();
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.