JavaScript 如何計算HTML標籤文本的行數

樓主有個需求,Span標籤內容超過2行,就隱藏剩餘文本,第二行文本末尾省略號表示,這就需要一個顯示更多 / 收起 的控制開關了

那我們怎麼知道文本到底有幾行呢?

實現原理是:行數 = 文本總高度 / 文本實際行高(非單純的lineHeight,而是包含margin, padding)

然後我們需要一個隱藏且絕對定位脫離文檔流的同樣內容的標籤,這個標籤是不讓用戶看到的,只是爲了方便計算文本總高度而存在

{/* 拷貝標籤,計算文本行數, 行數 = 文本總高度 / 每行文本高度 */}
// 假的文本標籤
<span
     className="hiddenContent"
     style={{ position: 'absolute', zIndex: -1, visibility: 'hidden' }}
>
     {content}
</span>

// 實際文本標籤
{!!isNeedShowMore && (
     <p className={`${styles['show-more']}`} onClick={this.showMore}>
         {this.state.isShow ? '收起' : '顯示更多'}
     </p>
)}

核心代碼:

computeLineCount = () => {
    const { currentIndex } = this.props
    const hiddenContent = document.getElementsByClassName('hiddenContent')[
      currentIndex
    ]  // 該文本所在的標籤 因爲標籤很多,所以需要制定索引值Index

    // 獲取實際文本樣式(包含lineHeight和height,fontSize等等)
    let style = window.getComputedStyle(hiddenContent, null)
    let fontSize = style.fontSize
    let lineHeight = (style.lineHeight === 'normal'
      ? fontSize
      : style.lineHeight
    ).replace('px', '')
    let height = style.height.replace('px', '')
    // console.warn('lineHeight, height:', lineHeight, height, height / lineHeight)
    if (height / lineHeight > 2) {
      this.setState({ isNeedShowMore: true })
    } else {
      this.setState({ isNeedShowMore: false })
    }
}

 

以下是具體實踐代碼:

CustomCard.js

import React from 'react'
import styles from './card.less'

export default class extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isShow: false,
      isNeedShowMore: true,
    }
  }

  componentDidMount() {
    this.computeLineCount()
  }

  computeLineCount = () => {
    const { currentIndex } = this.props
    const hiddenContent = document.getElementsByClassName('hiddenContent')[
      currentIndex
    ]

    // 獲取實際文本行高
    let style = window.getComputedStyle(hiddenContent, null)
    let fontSize = style.fontSize
    let lineHeight = (style.lineHeight === 'normal'
      ? fontSize
      : style.lineHeight
    ).replace('px', '')
    let height = style.height.replace('px', '')
    // console.warn('lineHeight, height:', lineHeight, height, height / lineHeight)
    if (height / lineHeight > 2) {
      this.setState({ isNeedShowMore: true })
    } else {
      this.setState({ isNeedShowMore: false })
    }
  }

  showMore = () => {
    const { isShow } = this.state
    const { currentIndex } = this.props
    const contentWrap = document.getElementsByClassName('contentWrap')[
      currentIndex
    ]

    if (contentWrap) {
      if (!isShow) {
        contentWrap.style.overflow = 'visible'
        contentWrap.style.webkitLineClamp = 'unset'
      } else {
        contentWrap.style.overflow = 'hidden'
        contentWrap.style.webkitLineClamp = '2'
      }
      this.setState({ isShow: !isShow })
    }
  }

  juadgePlatForm = () => {
    var u = navigator.userAgent
    if (u.indexOf('Android') > -1 || u.indexOf('Linux') > -1) {
      return 'Android'
      //安卓手機
    } else if (u.indexOf('iPhone') > -1) {
      return 'iPhone'
      //蘋果手機
    } else if (u.indexOf('Windows Phone') > -1) {
      //winphone手機
      return 'Windows Phone'
    }
  }

  render() {
    const { title, content, currentIndex } = this.props
    const { isNeedShowMore } = this.state
    let leftSize = '8px'
    if (currentIndex > 8 && currentIndex < 99) {
      const platform = this.juadgePlatForm()
      leftSize = platform === 'Android' ? '5px' : '5.7px'
    }
    if (currentIndex > 98) {
      leftSize = '2px'
    }

    return (
      <div style={{ padding: '6px 4px' }}>
        <div className={`${styles['shadow-wrap']}`}>
          <div
            style={{
              position: 'relative',
              width: '10%',
              marginRight: '5px',
            }}
          >
            <span
              style={{
                position: 'absolute',
                top: '1px',
                left: leftSize,
                color: 'white',
                fontSize: '12px',
              }}
            >
              {currentIndex + 1}
            </span>
            <img
              src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACoAAAAqCAYAAADFw8lbAAAC4ElEQVRYR+2Zz0tUURTHz7n3vTeOPxJUpBZRir+CaBFTwWTiSNCPTatoVYs2tSmrXc7AYP6MoIW0irZt/AsqG19ZQQuFCiu1tNpEIWJIIoMz98R9zAw5+uZd9c3whJnd8L7vez73e+85c2EQsj5Tr+9WGPHVqjjjmP3Mre8kkiTY6sLBUPdfVc8MzOyLgSNJEvcRIUgETNVgyzoCAYhjAHSzKRR+5+RjgaYgxwCgxOkFt58TwLLOIVjfFv6Qy9sCnTH7XgFAq9sQyn5Iz5raI6dygsozyROJPwXZbnuSxFJFTWkgcGXVToJzsd59CYbflVefJyFyrG1s65q3Bf1sDuznIL7lqb6ybRFUOSpFYTFRxaCUZelEiQhh4qGGWRMAvdZM4yODlRVcdALST92gkfrjkR9ytd4E1ZI3JBwRCNTZ4+YTt+c8DSphBdDCgVDkgedBJWxcY0M7AhQEPSqCKs+llDA9nqyuTzVTxqOY6GbjlHMydXsqJrqF8DZ8pZioW0mmfXZKouT3+cv2Bm+teLqZCGiiORQJyHQnn0SrdJ9xbc2OeWKOyhuSBmcb28JPJdzUy546FOySp0AR4DcQXG/sCA+nwaZjfWeAwVFlUESYYQjdIknLbjcOcEZAMN9QXj3+/03e2nZDvwqIuiMoAiYBaajE8Ifl4XYd0sbwy9voLrGiXQRgNesk2WdUpogCLzd0dL0pFOCv9/fKFudXDiHjrYhQumHdNKgG9DU7xU+xvsMcqT2fwMhAI0K/Yw0L9Hl/tc6gJTvFj6N3jmnITzuaFEIgQe3qTJv9LQB0oRAcjjVygU4ORw2j1ugksjk3ju4uCnKBWsN3tKcZCM8jQ+5i2c1bOYFaP2lmdDcnX5Ah7WFAa+fb5kvaviHACqPctutdrLVtqymzN4AAJwHQt27gb9vdZYPZ8cHK+FLyHEeoy1irbL3LHMp2a9L1MqhcUSZdolje/vRSjk5BaJpR7R+0+8PKTGuWBwAAAABJRU5ErkJggg=="
              style={{ width: '28px', height: '23px' }}
            />
          </div>
          <div
            style={{
              width: '90%',
            }}
          >
            <span className={`${styles['title']}`}>{title}</span>
            <span className={`${styles['content-wrap']} contentWrap`}>
              {content}
            </span>
            {/* 拷貝標籤,計算文本行數, 行數 = 文本總高度 / 每行文本高度 */}
            <span
              className="hiddenContent"
              style={{ position: 'absolute', zIndex: -1, visibility: 'hidden' }}
            >
              {content}
            </span>
            {!!isNeedShowMore && (
              <p className={`${styles['show-more']}`} onClick={this.showMore}>
                {this.state.isShow ? '收起' : '顯示更多'}
              </p>
            )}
          </div>
        </div>
      </div>
    )
  }
}

Card.less

.content-wrap {
  color: #666;
  font-size: 14px;
  line-height: 18px;
  display: inline-block;
  text-overflow: -o-ellipsis-lastline;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  /* autoprefixer: ignore next */
  -webkit-line-clamp: 2;
  line-clamp: 2;
  /* autoprefixer: ignore next */
  -webkit-box-orient: vertical;
  white-space: pre-wrap;
}
.shadow-wrap {
  box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.18);
  border-radius: 5px;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 12px;
}
.title {
  margin-bottom: 9px;
  color: #333;
  font-weight: bold;
  font-size: 16px;
  line-height: 20px;
  display: inline-block;
}
.show-more {
  margin: 10px 0 0 0;
  color: #999;
  font-size: 12px;
  text-align: right;
  line-height: 14px;
}

 

以下是參考自一修博主的文章,實踐有效且少計算邏輯,贊👍一個 

作者:一俢
鏈接:https://www.jianshu.com/p/6cedb432a763
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。 

在前端經常會遇到內容太多了,需要對多餘的內容進行截取並打上省略號的問題。CSS2 可以解決超出一行省略的問題,Chrome 可以通過-webkit-line-clamp來實現多行省略的問題,還是還需要配合其它的一些樣式屬性。解決這個問題的主要問題在於如何計算當前 DOM 內部的文本的行數

方案

要想知道文本的行數,那就需要知道文本的總高度和每一行的高度,總高度除以行高就是行數。當然總高度的計算必須是文字所在的 DOM 沒有對高度的限制,隨着文本的增加 DOM 要隨之變高才行;最後還要考慮 DOM 的樣式paddingmargin對高度的影響。這樣一來我們就可以計算出文本的行數了。總結一下我們需要如下幾步:

  • 克隆文本所在的 DOM;
  • 清除 DOM 的高度限制;
  • 獲取 DOM 的行高和高度;
  • 計算行數;
  • 去除克隆的 DOM。

清除文本在 DOM 內部高度的限制

拷貝文本所在的 DOM,將 DOM 的widthpadding-rightpadding-leftmargin-rightmargin-left保持和原有 DOM 一致;清除文本的heightpadding-toppadding-bottommargin-topmargin-bottom樣式,這樣一來文本就處於一個沒有高度限制的 DOM 中,而且高度不受paddingmargin的影響,並且它的寬度和原有寬度保持一致。

注意:代碼基於 zepto.js

 

var getRow = function(id) {
    var clone = $(id).clone().appendTo('body');

    // clear some style
    clone.css({
        "height":"auto",
        "padding-top": 0,
        "padding-bottom": 0,
        "margin-top": 0,
        "margin-bottom": 0
    });
    
    // todo...
};

獲取行高

獲取行高比較容易,直接通過window.getComputedStyle(element, null)可以獲取元素所有的最終所使用的樣式。

 

var getRow = function(id) {
    var clone = $(id).clone().appendTo('body');

    // clear some style
    // ...
    
    // get line-height
    var style = window.getComputedStyle(clone[0], null);
    var fontSize = style.fontSize;
    var lineHeight = style.lineHeight === "normal" ? fontSize : style.lineHeight;
    
    // todo...
};

計算行數

計算行數前必須要知道總高度,總高度獲取的方式太多了,可以和獲取行高一樣通過window.getComputedStyle(element, null),也可以通過document.clientHeight獲取,當然像 jQuery 等框架已經把獲取文檔對象的高度封裝好了。

注意:在計算之前需要把px轉成數字類型

 

var pxToNumber = function(px) {
   var num = Number(px.replace("px", ""));

   return num;
};

var getRow = function(id) {
    var clone = $(id).clone().appendTo('body');

    // clear some style
    // ...
    
    // get line-height
    // ...

    //get row count
    var height = style.height;
    var row = pxToNumber(height) / pxToNumber(lineHeight);
    
    clone.remove();
    
    return row;
};

最後

當然我們在計算高度的時候,這個克隆出來的 DOM 不能讓用戶看到,可以通過opacity: 0.0001或者visibility: hidden來隱藏它。

 

var pxToNumber = function(px) {
   var num = Number(px.replace("px", ""));

   return num;
};
var getRow = function(id) {
    var clone = $(id).clone().appendTo('body');

    // clear some style
    clone.css({
        "height":"auto",
        "padding-top": 0,
        "padding-bottom": 0,
        "margin-top": 0,
        "margin-bottom": 0,
        "visibility": "hidden"
    });
    
    // get line-height
    var style = window.getComputedStyle(clone[0], null);
    var fontSize = style.fontSize;
    var lineHeight = style.lineHeight === "normal" ? fontSize : style.lineHeight;

    //get row count
    var height = style.height;
    var row = pxToNumber(height) / pxToNumber(lineHeight);
    
    clone.remove();
    
    return row;
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章