Created
November 9, 2021 14:57
-
-
Save miraage/2480bc933ee0fbc8dbab20824e44f713 to your computer and use it in GitHub Desktop.
Multiline ellipsis
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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