基於clamp.js封裝vue指令,處理多行文字的溢位

OasisCraft發表於2018-10-25

最近做專案時,遇到了一個需求:要求div裡文字在兩行顯示,div的寬度是固定的,如果溢位的話就顯示省略號。單行文字的溢位問題,我們都很熟悉,只要新增以下css屬性就ok:

  overflow: hidden;
  white-space: nowrap; //段落中文字不換行
  text-overflow: ellipsis;

但是多行文字的溢位怎麼處理呢?

  • 查了資料之後發現還是有辦法的

    在WebKit瀏覽器或移動端(絕大部分是WebKit核心的瀏覽器)的頁面實現比較簡單,可以直接使用WebKit的CSS擴充套件屬性(WebKit是私有屬性)-webkit-line-clamp ;注意:這是一個 不規範的屬性(unsupported WebKit property),它沒有出現在 CSS 規範草案中。-webkit-line-clamp用來限制在一個塊元素顯示的文字的行數。 為了實現該效果,它需要組合其他的WebKit屬性。

  • 我們用一下程式碼即可實現:
    overflow : hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  • 但是這樣處理會遇到相容性問題,在Firefox瀏覽器上就不起作用。
  • 為了解決相容性問題,有一個clamp.js[https://www.npmjs.com/package…]很好的解決這個問題。
  • 為了更好的跟Vue相結合,今天我們就封裝一個v-clamp的指令,來方便的解決這個問題。

    // 註冊一個全域性自定義指令 `v-clamp`
    Vue.directive(`clamp`, {
      // 當被繫結的元素插入到 DOM 中時……
      update: function (el, binding) {
          function clamp(element, options) {
              options = options || {};
          
              var self = this,
                win = window,
                opt = {
                  clamp: options.clamp || 2,
                  useNativeClamp: typeof(options.useNativeClamp) != `undefined` ? options.useNativeClamp : true,
                  splitOnChars: options.splitOnChars || [`.`, `-`, `–`, `—`, ` `], //Split on sentences (periods), hypens, en-dashes, em-dashes, and words (spaces).
                  animate: options.animate || false,
                  truncationChar: options.truncationChar || `…`,
                  truncationHTML: options.truncationHTML
                },
          
                sty = element.style,
                originalText = element.innerHTML,
          
                supportsNativeClamp = typeof(element.style.webkitLineClamp) != `undefined`,
                clampValue = opt.clamp,
                isCSSValue = clampValue.indexOf && (clampValue.indexOf(`px`) > -1 || clampValue.indexOf(`em`) > -1),
                truncationHTMLContainer;
          
              if (opt.truncationHTML) {
                truncationHTMLContainer = document.createElement(`span`);
                truncationHTMLContainer.innerHTML = opt.truncationHTML;
              }
          
          
              // UTILITY FUNCTIONS __________________________________________________________
          
              /**
               * Return the current style for an element.
               * @param {HTMLElement} elem The element to compute.
               * @param {string} prop The style property.
               * @returns {number}
               */
              function computeStyle(elem, prop) {
                if (!win.getComputedStyle) {
                  win.getComputedStyle = function(el, pseudo) {
                    this.el = el;
                    this.getPropertyValue = function(prop) {
                      var re = /(-([a-z]){1})/g;
                      if (prop == `float`) prop = `styleFloat`;
                      if (re.test(prop)) {
                        prop = prop.replace(re, function() {
                          return arguments[2].toUpperCase();
                        });
                      }
                      return el.currentStyle && el.currentStyle[prop] ? el.currentStyle[prop] : null;
                    };
                    return this;
                  };
                }
          
                return win.getComputedStyle(elem, null).getPropertyValue(prop);
              }
          
              /**
               * Returns the maximum number of lines of text that should be rendered based
               * on the current height of the element and the line-height of the text.
               */
              function getMaxLines(height) {
                var availHeight = height || element.clientHeight,
                  lineHeight = getLineHeight(element);
          
                return Math.max(Math.floor(availHeight / lineHeight), 0);
              }
          
              /**
               * Returns the maximum height a given element should have based on the line-
               * height of the text and the given clamp value.
               */
              function getMaxHeight(clmp) {
                var lineHeight = getLineHeight(element);
                return lineHeight * clmp;
              }
          
              /**
               * Returns the line-height of an element as an integer.
               */
              function getLineHeight(elem) {
                var lh = computeStyle(elem, `line-height`);
                if (lh == `normal`) {
                  // Normal line heights vary from browser to browser. The spec recommends
                  // a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff.
                  lh = parseInt(computeStyle(elem, `font-size`)) * 1.2;
                }
                return parseInt(lh);
              }
          
          
              // MEAT AND POTATOES (MMMM, POTATOES...) ______________________________________
              var splitOnChars = opt.splitOnChars.slice(0),
                splitChar = splitOnChars[0],
                chunks,
                lastChunk;
          
              /**
               * Gets an element`s last child. That may be another node or a node`s contents.
               */
              function getLastChild(elem) {
                //Current element has children, need to go deeper and get last child as a text node
                if (elem.lastChild.children && elem.lastChild.children.length > 0) {
                  return getLastChild(Array.prototype.slice.call(elem.children).pop());
                }
                //This is the absolute last child, a text node, but something`s wrong with it. Remove it and keep trying
                else if (!elem.lastChild || !elem.lastChild.nodeValue || elem.lastChild.nodeValue === `` || elem.lastChild.nodeValue == opt.truncationChar) {
                  elem.lastChild.parentNode.removeChild(elem.lastChild);
                  return getLastChild(element);
                }
                //This is the last child we want, return it
                else {
                  return elem.lastChild;
                }
              }
          
              /**
               * Removes one character at a time from the text until its width or
               * height is beneath the passed-in max param.
               */
              function truncate(target, maxHeight) {
                if (!maxHeight) {
                  return;
                }
          
                /**
                 * Resets global variables.
                 */
                function reset() {
                  splitOnChars = opt.splitOnChars.slice(0);
                  splitChar = splitOnChars[0];
                  chunks = null;
                  lastChunk = null;
                }
          
                var nodeValue = target.nodeValue.replace(opt.truncationChar, ``);
          
                //Grab the next chunks
                if (!chunks) {
                  //If there are more characters to try, grab the next one
                  if (splitOnChars.length > 0) {
                    splitChar = splitOnChars.shift();
                  }
                  //No characters to chunk by. Go character-by-character
                  else {
                    splitChar = ``;
                  }
          
                  chunks = nodeValue.split(splitChar);
                }
          
                //If there are chunks left to remove, remove the last one and see if
                // the nodeValue fits.
                if (chunks.length > 1) {
                  // console.log(`chunks`, chunks);
                  lastChunk = chunks.pop();
                  // console.log(`lastChunk`, lastChunk);
                  applyEllipsis(target, chunks.join(splitChar));
                }
                //No more chunks can be removed using this character
                else {
                  chunks = null;
                }
          
                //Insert the custom HTML before the truncation character
                if (truncationHTMLContainer) {
                  target.nodeValue = target.nodeValue.replace(opt.truncationChar, ``);
                  element.innerHTML = target.nodeValue + ` ` + truncationHTMLContainer.innerHTML + opt.truncationChar;
                }
          
                //Search produced valid chunks
                if (chunks) {
                  //It fits
                  if (element.clientHeight <= maxHeight) {
                    //There`s still more characters to try splitting on, not quite done yet
                    if (splitOnChars.length >= 0 && splitChar !== ``) {
                      applyEllipsis(target, chunks.join(splitChar) + splitChar + lastChunk);
                      chunks = null;
                    }
                    //Finished!
                    else {
                      return element.innerHTML;
                    }
                  }
                }
                //No valid chunks produced
                else {
                  //No valid chunks even when splitting by letter, time to move
                  //on to the next node
                  if (splitChar === ``) {
                    applyEllipsis(target, ``);
                    target = getLastChild(element);
          
                    reset();
                  }
                }
          
                //If you get here it means still too big, let`s keep truncating
                if (opt.animate) {
                  setTimeout(function() {
                    truncate(target, maxHeight);
                  }, opt.animate === true ? 10 : opt.animate);
                } else {
                  return truncate(target, maxHeight);
                }
              }
          
              function applyEllipsis(elem, str) {
                elem.nodeValue = str + opt.truncationChar;
              }
          
          
              // CONSTRUCTOR ________________________________________________________________
          
              if (clampValue == `auto`) {
                clampValue = getMaxLines();
              } else if (isCSSValue) {
                clampValue = getMaxLines(parseInt(clampValue));
              }
          
              var clampedText;
              if (supportsNativeClamp && opt.useNativeClamp) {
                sty.overflow = `hidden`;
                sty.textOverflow = `ellipsis`;
                sty.webkitBoxOrient = `vertical`;
                sty.display = `-webkit-box`;
                sty.webkitLineClamp = clampValue;
          
                if (isCSSValue) {
                  sty.height = opt.clamp + `px`;
                }
              } else {
                var height = getMaxHeight(clampValue);
                if (height <= element.clientHeight) {
                  console.log(getLastChild(element));
                  clampedText = truncate(getLastChild(element), height);
                }
              }
          
              return {
                `original`: originalText,
                `clamped`: clampedText
              };
            }
    
           clamp(el,{clamp: 2}) 
    
    
      }
    })
    
    • 其實很簡單,僅僅是把clamp.js中的函式搬移了過來。然後就可以像這樣來使用:

        <div class="txt" v-clamp>很抱歉!沒有搜尋到相關模板很抱歉!沒有搜尋到相關模板很抱歉!沒有搜尋到相關模板很抱歉!沒有搜尋到相關模板</div>   

相關文章