Skip to content

Instantly share code, notes, and snippets.

@miraage
Created November 9, 2021 14:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save miraage/2480bc933ee0fbc8dbab20824e44f713 to your computer and use it in GitHub Desktop.
Save miraage/2480bc933ee0fbc8dbab20824e44f713 to your computer and use it in GitHub Desktop.
Multiline ellipsis
// https://stackoverflow.com/a/55605049
export function getLineBreaks(node: Node): string[] {
// we only deal with TextNodes
if (!node || !node.parentNode || node.nodeType !== 3 || !node.textContent) return [];
// our Range object form which we'll get the characters positions
const range = document.createRange();
// here we'll store all our lines
const lines = [];
// begin at the first char
range.setStart(node, 0);
// initial position
let prevBottom = range.getBoundingClientRect().bottom;
let str = node.textContent;
let current = 1; // we already got index 0
let lastFound = 0;
let bottom = 0;
// iterate over all characters
while (current <= str.length) {
// move our cursor
range.setStart(node, current);
if (current < str.length - 1) range.setEnd(node, current + 1);
bottom = range.getBoundingClientRect().bottom;
if (bottom > prevBottom) {
// line break
lines.push(
str.substr(lastFound, current - lastFound) // text content
);
prevBottom = bottom;
lastFound = current;
}
current++;
}
// push the last line
lines.push(str.substr(lastFound));
return lines;
}
import { useRef, useState, useLayoutEffect } from 'react';
import { getLineBreaks } frmo './get-line-breaks';
interface Props {
lineHeight: string | number; // will be used in calc()
maxLines: number;
children: string;
}
interface State {
initialized: boolean;
replacement: string | null;
}
const initialState: State = {
initialized: false,
replacement: null
};
export const MultilineEllipsis: FC<> = ({ lineHeight, maxLines, children }) => {
const ref = useRef<HTMLDivElement>();
const [state, setState] = useState<State>(initialState);
useLayoutEffect(() => {
if (!ref.current || state.initialized) {
return;
}
const lines = getLineBreaks(ref.current.childNodes[0]);
if (lines.length > maxLines) {
lines[maxLines - 1] = lines[maxLines - 1].slice(0, -3) + "...";
const replacement = lines.slice(0, maxLines).join("");
setState({
initialized: true,
replacement
});
}
}, [maxLines, state.initialized]);
const style = {
maxHeight: `calc(${lineHeight} * ${maxLines})`,
overflow: 'hidden',
opacity: Number(state.initialized)
};
return (
<div style={style} ref={ref}>
{state.replacement || children}
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment