最近做專案時,遇到了一個需求:要求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>
-