最近做项目时,遇到了一个需求:要求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>