前言
在前端日常开发中,省略号效果是常见的需求,遇到就复制粘贴一下代码,就完成了任务。但有的时候元素宽高是一个变值,这个时候设置-webkit-line-clamp
为一个定值显然是不行的。我们要根据元素宽高
、字体大小
和文字行高
来动态的设置-webkit-line-clamp
。后面有完整代码。
正文
还是先来看看效果,有几个难点
- 如何确定每一行高度
- 什么时候需要出现省略号
- 确定
-webkit-line-clamp
的值
确定每一行高度
我们要确定每一行的实际高度为多少,我们可以创建一个元素插入到文字中,然后获取其高度、字体大小、行高进行处理。
我们获取的行高一般有以下几种情况
normal
,我们将每一行高度设置为创建元素的高度。- 当满足
new RegExp('px')
,直接使用 - 当满足
new RegExp('%')
,我们使用字体大小 * 百分比 - 还有的情况(1.4|2em),我们取到的
lineHeight
都是以new RegExp('px')
这个形式,直接使用
判断文字是否需要省略
我们只要判断实际文字内容的高度
与元素高度
的大小,实际文字内容的高度
>元素高度
就是需要省略。
文字范围高度rangeHeight
我们会用createRange
来计算。
js
const range = document.createRange();
range.setStart(dom, 0);
range.setEnd(dom, dom.childNodes.length);
let rangeHeight = range.getBoundingClientRect().height;
而实际文字内容的高度
我们还需要加上漏算的lineHeight部分
,
-
实际文字内容的高度=
rangeHeight + (rowHeight - fontHeight)
-
当
rangeHeight + (rowHeight.current - fontHeight.current) > textContainerHeight
就需要省略
需要省略的行数
自适应宽高
自适应我们只需要使用ResizeObserver
去观察元素大小变化就行了
完整代码
tsx
import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
import './Ellipsis.scss'
export default function Index() {
const textDom = useRef<HTMLDivElement | null>(null)
const parentDom = useRef<HTMLDivElement | null>(null)
const rowHeight = useRef(0)
const maxNum = 999999999;
const [row, setRow] = useState(maxNum)
const [style, setStyle] = useState<React.CSSProperties>({});
const [height, setHeight] = useState('100%')
const fontHeight = useRef(0)
/** 获取应该省略几行*/
const getWebkitLineClamp = useCallback((parentDom: HTMLElement, dom: HTMLElement) => {
let parentInfo = parentDom.getBoundingClientRect()
const range = document.createRange();
range.setStart(dom, 0);
range.setEnd(dom, dom.childNodes.length);
let rangeHeight = range.getBoundingClientRect().height;
let textContainerHeight = dom.getBoundingClientRect().height;
setHeight(`100%`)
if (rangeHeight + (rowHeight.current - fontHeight.current) > textContainerHeight) {
let rowNum = Math.floor(parentInfo.height / rowHeight.current)
setHeight(`${rowNum * rowHeight.current}px`)
return rowNum
}
return maxNum
}, [])
/** 获取实际row的高度*/
const getRowHeight = useCallback((parentDom: HTMLElement, dom: HTMLElement) => {
const styles = window.getComputedStyle(dom);
let cssText = styles.cssText;
if (!cssText) {
cssText = Array.from(styles).reduce((str, property) => {
return `${str}${property}:${styles.getPropertyValue(property)};`;
}, '');
}
cssText += 'display:inline;opacity:0; '
const textDom = document.createElement('div');
textDom.innerHTML = 'as';
textDom.style.cssText = cssText;
(parentDom as any).appendChild(textDom);
/** 获取样式*/
let styleInfo = window.getComputedStyle(textDom);
let textInfo = textDom.getBoundingClientRect();
let lineHeight = styleInfo.lineHeight;
let fontSize = styleInfo.fontSize;
let resultLineHeight = 0;
fontHeight.current = textInfo.height;
/** 获取行高*/
let regular = new RegExp('px')
let regularOne = new RegExp('%')
if (lineHeight === 'normal') {
resultLineHeight = textInfo.height
} else if (regular.test(lineHeight)) {
resultLineHeight = Number(lineHeight.substring(0, lineHeight.length - 2))
} else if (regularOne.test(lineHeight)) {
resultLineHeight = Number(fontSize.substring(0, fontSize.length - 2)) * Number(lineHeight.substring(0, lineHeight.length - 1)) / 100
} else {
resultLineHeight = Number(lineHeight.substring(0, lineHeight.length - 2))
}
rowHeight.current = resultLineHeight;
parentDom.removeChild(textDom);
return resultLineHeight
}, [])
useEffect(() => {
if (textDom.current === null || parentDom.current === null) {
return
}
getRowHeight(parentDom.current, textDom.current)
const resizeObserver = new ResizeObserver(entries => {
setRow(getWebkitLineClamp(parentDom.current as HTMLDivElement, textDom.current as HTMLDivElement))
});
resizeObserver.observe(parentDom.current);
}, [])
useEffect(() => {
setStyle({
overflow: "hidden",
textOverflow: "ellipsis",
display: "-webkit-box",
"WebkitLineClamp": row,
"WebkitBoxOrient": "vertical",
height: "100%",
backgroundColor: "red",
wordBreak: "break-all",
})
}, [row])
return (
<>
<div style={{
height: '50vh',
minHeight: `${rowHeight.current}px`,
width: '50vw',
fontSize: "32px",
lineHeight: "60px"
}} ref={parentDom}>
<div ref={textDom}
style={{
...style,
height: height,
}}
>蓦然回首,那每一个瞬间的感动都溢满了心扉,高悬的心墙上盛载着青涩与酸甜苦辣的果实,温和而善良的眼神使爱的节日变得更加温存而充盈。
</div>
</div>
<div>sadasd</div>
</>
)
}
结语
感兴趣的可以去试试