文字根据宽高自适应省略号

前言

在前端日常开发中,省略号效果是常见的需求,遇到就复制粘贴一下代码,就完成了任务。但有的时候元素宽高是一个变值,这个时候设置-webkit-line-clamp为一个定值显然是不行的。我们要根据元素宽高字体大小文字行高来动态的设置-webkit-line-clamp。后面有完整代码。

正文

还是先来看看效果,有几个难点

  • 如何确定每一行高度
  • 什么时候需要出现省略号
  • 确定-webkit-line-clamp的值

确定每一行高度

我们要确定每一行的实际高度为多少,我们可以创建一个元素插入到文字中,然后获取其高度、字体大小、行高进行处理。

我们获取的行高一般有以下几种情况

  1. normal,我们将每一行高度设置为创建元素的高度。
  2. 当满足new RegExp('px'),直接使用
  3. 当满足new RegExp('%'),我们使用字体大小 * 百分比
  4. 还有的情况(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>
    </>
  )
}

结语

感兴趣的可以去试试

相关推荐
崔庆才丨静觅12 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了13 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅13 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅14 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅14 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊14 小时前
jwt介绍
前端
爱敲代码的小鱼14 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax