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

前言

在前端日常开发中,省略号效果是常见的需求,遇到就复制粘贴一下代码,就完成了任务。但有的时候元素宽高是一个变值,这个时候设置-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>
    </>
  )
}

结语

感兴趣的可以去试试

相关推荐
新中地GIS开发老师1 小时前
25考研希望渺茫,工作 VS 二战,怎么选?
javascript·学习·考研·arcgis·地理信息科学·地信
萧大侠jdeps1 小时前
Vue 3 与 Tauri 集成开发跨端APP
前端·javascript·vue.js·tauri
JYeontu2 小时前
实现一个动态脱敏指令,输入时候显示真实数据,展示的时候进行脱敏
前端·javascript·vue.js
发呆的薇薇°2 小时前
react里使用Day.js显示时间
前端·javascript·react.js
嘤嘤嘤2 小时前
基于大模型技术构建的 GitHub Assistant
前端·github
KeepCatch2 小时前
CSS 动画与过渡效果
前端
跑跑快跑2 小时前
React vite + less
前端·react.js·less
web136885658712 小时前
ctfshow_web入门_命令执行_web29-web39
前端
GISer_Jing2 小时前
前端面试题合集(一)——HTML/CSS/Javascript/ES6
前端·javascript·html
清岚_lxn2 小时前
es6 字符串每隔几个中间插入一个逗号
前端·javascript·算法