跨浏览器多行文本溢出,在宽度和高度上附加省略号固定`<div>`</div>

时间:2010-08-04 10:11:32

标签: javascript jquery html css

我为这个问题制作了一张图片,以便更容易理解。

是否可以在具有固定宽度和多行的<div>上创建省略号?

text-overflow

我已经在这里和那里尝试了一些jQuery插件,但找不到我正在寻找的那个。有什么建议?想法?

25 个答案:

答案 0 :(得分:86)

只是一个快速的基本想法。

我正在使用以下标记进行测试:

<div id="fos">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin nisi ligula, dapibus a volutpat sit amet, mattis et dui. Nunc porttitor accumsan orci id luctus. Phasellus ipsum metus, tincidunt non rhoncus id, dictum a lectus. Nam sed ipsum a lacus sodales eleifend. Vestibulum lorem felis, rhoncus elementum vestibulum eget, dictum ut velit. Nullam venenatis, elit in suscipit imperdiet, orci purus posuere mauris, quis adipiscing ipsum urna ac quam.</p>  
</div>

和CSS:

#fos { width: 300px; height: 190px; overflow: hidden; }
#fos p { padding: 10px; margin: 0; }

应用此jQuery将实现所需的结果:

var $p = $('#fos p');
var divh = $('#fos').height();
while ($p.outerHeight() > divh) {
    $p.text(function (index, text) {
        return text.replace(/\W*\s(\S)*$/, '...');
    });
}

它反复尝试删除文本的最后一个单词,直到达到所需的大小。因为溢出:隐藏;这个过程仍然是不可见的,即使关闭了JS,结果仍然是“视觉上正确的”(当然没有“......”)。

如果将它与服务器端的合理截断相结合(只留下很小的开销),那么它将更快地运行:)。

同样,这不是一个完整的解决方案,只是一个想法。

更新:添加了jsFiddle Demo

答案 1 :(得分:67)

试用jQuery.dotdotdot插件。

$(".ellipsis").dotdotdot();

答案 2 :(得分:18)

用于“线夹”的Javascript库

请注意,“线夹”也称为“多线块上的省略号”或“垂直省略号”。

github.com/BeSite/jQuery.dotdotdot

github.com/josephschmitt/Clamp.js

以下是我尚未调查的一些内容:

用于线夹的CSS解决方案

有一些CSS解决方案,但最简单的用途-webkit-line-clamp which has poor browser support。查看jsfiddle.net/AdrienBe/jthu55v7/

上的实时演示

为了使用CSS只能实现这一点,许多人付出了巨大的努力。查看有关它的文章和问题:

我建议

保持简单。除非您有足够的时间专注于此功能,否则请选择最简单的功能。经过测试的解决方案:简单的CSS或经过良好测试的JavaScript库。

寻找花哨/复杂/高度定制的东西&amp;你将为此付出代价。

其他人做什么

像Airbnb一样淡出可能是一个很好的解决方案。它可能是基本的CSS加上基本的jQuery。实际上,它似乎与this solution on CSSTricks

非常相似

AirBnb "read more" solution

哦,如果你寻找设计灵感:

答案 3 :(得分:6)

HTML中没有这样的功能,这非常令人沮丧。

我已经开发了一个library来解决这个问题。

  • 多行文字溢出:省略号
  • 使用不支持技术的多行文字:SVG,Canvas例如
  • 在您的SVG文本,HTML渲染和PDF导出中具有完全相同的换行符,例如

查看my site屏幕截图,教程和下载链接。

答案 4 :(得分:4)

基于bažmegakapa解决方案的纯JS解决方案,以及一些清理方法,以解释那些试图给出高度/最大高度小于元素lineHeight的人:

  var truncationEl = document.getElementById('truncation-test');
  function calculateTruncation(el) {
    var text;
    while(el.clientHeight < el.scrollHeight) {
      text = el.innerHTML.trim();
      if(text.split(' ').length <= 1) {
        break;
      }
      el.innerHTML = text.replace(/\W*\s(\S)*$/, '...');
    }
  }

  calculateTruncation(truncationEl);

答案 5 :(得分:4)

我有一个效果很好的解决方案,但是使用渐变的省略号。优点是您不必进行任何JavaScript计算,它适用于包括表格单元格的可变宽度容器。它使用了几个额外的div,但它很容易实现。

http://salzerdesign.com/blog/?p=453

编辑:对不起,我不知道这个链接是不够的。解决方案是在文本周围放置div,并设置div的样式以控制溢出。在div内部放置了另一个具有“淡入淡出”渐变的div,可以通过使用CSS或图像(对于旧IE)来实现。渐变从透明到表格单元格的背景颜色,并且比省略号略宽。如果文本很长并且溢出,它会在“淡入淡出”div下面看起来“淡出”。如果文字很短,则淡入淡出是不可见的,所以没有问题。通过将容器的高度设置为文本行高度的倍数,可以调整两个容器以显示一行或多行。 “淡入淡出”div可以定位为仅覆盖最后一行。

答案 6 :(得分:3)

以下是实现此目的的纯CSS方法:http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/

以下是摘要:

enter image description here

<html>
<head>
<style>
    html, body, p { margin: 0; padding: 0; font-family: sans-serif;}

    .ellipsis {
        overflow: hidden;
        height: 200px;
        line-height: 25px;
        margin: 20px;
        border: 5px solid #AAA; }

    .ellipsis:before {
        content:"";
        float: left;
        width: 5px; height: 200px; }

    .ellipsis > *:first-child {
        float: right;
        width: 100%;
        margin-left: -5px; }        

    .ellipsis:after {
        content: "\02026";  

        box-sizing: content-box;
        -webkit-box-sizing: content-box;
        -moz-box-sizing: content-box;

        float: right; position: relative;
        top: -25px; left: 100%; 
        width: 3em; margin-left: -3em;
        padding-right: 5px;

        text-align: right;

        background: -webkit-gradient(linear, left top, right top,
            from(rgba(255, 255, 255, 0)), to(white), color-stop(50%, white));
        background: -moz-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);           
        background: -o-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
        background: -ms-linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white);
        background: linear-gradient(to right, rgba(255, 255, 255, 0), white 50%, white); }
</style>
</head>
<body>
    <div class="ellipsis">
        <div>
            <p>Call me Ishmael.....</p> 
        </div>
    </div>
</body>
</html>

答案 7 :(得分:2)

Adrien Be's answer中找到了这个简短的仅CSS 解决方案:

.line-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical; 
  overflow: hidden; 
}

截至 2020年3月 browser support 95.3%,IE和Opera Mini中不支持。适用于Chrome,Safari,Firefox和Edge。

答案 8 :(得分:1)

上面提到的dotdotdot jQuery plugin很适合使用angular:

(function (angular) {
angular.module('app')
    .directive('appEllipsis', [
        "$log", "$timeout", function ($log, $timeout) {
            return {
                restrict: 'A',
                scope: false,
                link: function (scope, element, attrs) {

                    // let the angular data binding run first
                    $timeout(function() {
                        element.dotdotdot({
                            watch: "window"
                        });
                    });
                }
            }

        }
    ]);
})(window.angular);

相应的标记是:

<p app-ellipsis>{{ selectedItem.Description }}</p>

答案 9 :(得分:1)

可能很晚但是使用SCSS你可以声明一个类似的函数:

@mixin clamp-text($lines, $line-height) {
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: $lines;
  line-height: $line-height;
  max-height: unquote('#{$line-height*$lines}em');

  @-moz-document url-prefix() {
    position: relative;
    height: unquote('#{$line-height*$lines}em');

    &::after {
      content: '';
      text-align: right;
      position: absolute;
      bottom: 0;
      right: 0;
      width: 30%;
      height: unquote('#{$line-height}em');
      background: linear-gradient(
        to right,
        rgba(255, 255, 255, 0),
        rgba(255, 255, 255, 1) 50%
      );
    }
  }
}

并使用它:

.foo {
    @include clamp-text(1, 1.4);
}

将文本截断为一行,并知道它的行高为1.4。 预期的输出是chrome,最后使用...进行渲染,最后使用FF进行渲染

<强>火狐

enter image description here

<强>铬

enter image description here

答案 10 :(得分:1)

编辑: 来自Shave这是一个JS插件,可以很好地根据给定的最大高度进行多行文本截断。它使用二分搜索来找到最佳断点。绝对值得调查。

原始回答:

我不得不为这个问题想出一个vanilla JS解决方案。在我工作的情况下,我必须将长的产品名称放入有限的宽度和超过两行;如果需要,用省略号截断。

我使用各种SO帖子的答案来烹饪符合我需求的东西。策略如下:

  1. 计算所需字体大小的字体变体的平均字符宽度。
  2. 计算容器的宽度
  3. 计算容器中一行的字符数
  4. 根据一行中适合的字符数和文本应包裹的行数计算截断字符串的字符数。
  5. 根据先前的计算截断输入文本(通过省略号添加额外字符的因子)并将“...”追加到末尾
  6. 代码示例:

    /**
     * Helper to get the average width of a character in px
     * NOTE: Ensure this is used only AFTER font files are loaded (after page load)
     * @param {DOM element} parentElement 
     * @param {string} fontSize 
     */
    function getAverageCharacterWidth(parentElement, fontSize) {
        var textSample = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()";
        parentElement = parentElement || document.body;
        fontSize = fontSize || "1rem";
        var div = document.createElement('div');
        div.style.width = "auto";
        div.style.height = "auto";
        div.style.fontSize = fontSize;
        div.style.whiteSpace = "nowrap";
        div.style.position = "absolute";
        div.innerHTML = textSample;
        parentElement.appendChild(div);
    
        var pixels = Math.ceil((div.clientWidth + 1) / textSample.length);
        parentElement.removeChild(div);
        return pixels;
    }
    
    /**
     * Helper to truncate text to fit into a given width over a specified number of lines
     * @param {string} text Text to truncate
     * @param {string} oneChar Average width of one character in px
     * @param {number} pxWidth Width of the container (adjusted for padding)
     * @param {number} lineCount Number of lines to span over
     * @param {number} pad Adjust this to ensure optimum fit in containers. Use a negative value to Increase length of truncation, positive values to decrease it.
     */
    function truncateTextForDisplay(text, oneChar, pxWidth, lineCount, pad) {
        var ellipsisPadding = isNaN(pad) ? 0 : pad;
        var charsPerLine = Math.floor(pxWidth / oneChar);
        var allowedCount = (charsPerLine * (lineCount)) - ellipsisPadding;
        return text.substr(0, allowedCount) + "...";
    }
    
    
    //SAMPLE USAGE:
    var rawContainer = document.getElementById("raw");
    var clipContainer1 = document.getElementById("clip-container-1");
    var clipContainer2 = document.getElementById("clip-container-2");
    
    //Get the text to be truncated
    var text=rawContainer.innerHTML;
    
    //Find the average width of a character
    //Note: Ideally, call getAverageCharacterWidth only once and reuse the value for the same font and font size as this is an expensive DOM operation
    var oneChar = getAverageCharacterWidth();
    
    //Get the container width
    var pxWidth = clipContainer1.clientWidth;
    
    //Number of lines to span over
    var lineCount = 2;
    
    //Truncate without padding
    clipContainer1.innerHTML = truncateTextForDisplay(text, oneChar, pxWidth, lineCount);
    
    //Truncate with negative padding value to adjust for particular font and font size
    clipContainer2.innerHTML = truncateTextForDisplay(text, oneChar, pxWidth, lineCount,-10);
    .container{
      display: inline-block;
      width: 200px;
      overflow: hidden;
      height: auto;
      border: 1px dotted black;
      padding: 10px;
      }
    <h4>Untruncated</h4>
    <div id="raw" class="container">
    This is super long text which needs to be clipped to the correct length with ellipsis spanning over two lines
    </div>
    <h4>Truncated</h4>
    <div id="clip-container-1" class="container">
    </div>
    <h4>Truncated with Padding Tweak</h4>
    <div id="clip-container-2" class="container">
    </div>

    PS:

    1. 如果截断只在一行上,那么使用文本溢出的纯CSS方法:省略号更整洁
    2. 没有固定宽度的字体可能导致截断发生得太早或太晚(因为不同的字符具有不同的宽度)。使用pad参数有助于在某些情况下缓解这种情况,但不会是万无一失的:)
    3. 在我恢复笔记本电脑后需要添加链接和对原始帖子的引用(需要历史记录)
    4. PPS:刚刚意识到这与@DanMan和@ st.never建议的方法非常相似。查看代码片段以获取实现示例。

答案 11 :(得分:1)

Pure JS demo (without jQuery and 'while' loop)

当我搜索多行省略号问题的解决方案时,我很惊讶没有jQuery没有任何好的。还有一些解决方案基于&#39;而#39;循环,但我认为由于可能进入无限循环,它们不是有效和危险的。所以我写了这段代码:

function ellipsizeTextBox(el) {
  if (el.scrollHeight <= el.offsetHeight) {
    return;
  }

  let wordArray = el.innerHTML.split(' ');
  const wordsLength = wordArray.length;
  let activeWord;
  let activePhrase;
  let isEllipsed = false;

  for (let i = 0; i < wordsLength; i++) {
    if (el.scrollHeight > el.offsetHeight) {
      activeWord = wordArray.pop();
      el.innerHTML = activePhrase = wordArray.join(' ');
    } else {
      break;
    }
  }

  let charsArray = activeWord.split('');
  const charsLength = charsArray.length;

  for (let i = 0; i < charsLength; i++) {
    if (el.scrollHeight > el.offsetHeight) {
      charsArray.pop();
      el.innerHTML = activePhrase + ' ' + charsArray.join('')  + '...';
      isEllipsed = true;
    } else {
      break;
    }
  }

  if (!isEllipsed) {
    activePhrase = el.innerHTML;

    let phraseArr = activePhrase.split('');
    phraseArr = phraseArr.slice(0, phraseArr.length - 3)
    el.innerHTML = phraseArr.join('') + '...';
  }
}

let el = document.getElementById('ellipsed');

ellipsizeTextBox(el);

答案 12 :(得分:1)

这是一个可以在紧要关头使用的vanilla JavaScript解决方案:

&#13;
&#13;
// @param 1 = element containing text to truncate
// @param 2 = the maximum number of lines to show
function limitLines(el, nLines) {
  var nHeight,
    el2 = el.cloneNode(true);
  // Create clone to determine line height
  el2.style.position = 'absolute';
  el2.style.top = '0';
  el2.style.width = '10%';
  el2.style.overflow = 'hidden';
  el2.style.visibility = 'hidden';
  el2.style.whiteSpace = 'nowrap';
  el.parentNode.appendChild(el2);
  nHeight = (el2.clientHeight+2)*nLines; // Add 2 pixels of slack
  // Clean up
  el.parentNode.removeChild(el2);
  el2 = null;
  // Truncate until desired nLines reached
  if (el.clientHeight > nHeight) {
    var i = 0,
      imax = nLines * 35;
    while (el.clientHeight > nHeight) {
      el.innerHTML = el.textContent.slice(0, -2) + '&hellip;';
      ++i;
      // Prevent infinite loop in "print" media query caused by
      // Bootstrap 3 CSS: a[href]:after { content:" (" attr(href) ")"; }
      if (i===imax) break;
    }
  }
}

limitLines(document.getElementById('target'), 7);
&#13;
#test {
  width: 320px;
  font-size: 18px;
}
&#13;
<div id="test">
  <p>Paragraph 1</p>
  <p id="target">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  <p>Paragraph 3</p>
</div>
&#13;
&#13;
&#13;

您可以在下面的codepen中使用它。尝试更改CSS面板中的字体大小,并在HTML面板中进行次要编辑(例如,在某处添加额外的空间)以更新结果。无论字体大小如何,中间段落应始终截断为传递给limitLines()的第二个参数中的行数。

Codepen: http://codepen.io/thdoan/pen/BoXbEK

答案 13 :(得分:1)

这个问题不是一个确切的答案,但是当我尝试做非常相似的事情时,我遇到了这个页面,但是想要添加“查看更多”的链接,而不仅仅是一个简单的省略号。这是一个jQuery函数,它将为溢出容器的文本添加“更多”链接。就个人而言,我正在将它与Bootstrap一起使用,但当然它可以不用。

Example more screenshot

要使用,请将文本放在容器中,如下所示:

<div class="more-less">
    <div class="more-block">
        <p>The long text goes in here</p>
    </div>
</div>

当添加以下jQuery函数时,任何大于adjustheight值的div都将被截断并添加“More”链接。

$(function(){
    var adjustheight = 60;
    var moreText = '+ More';
    var lessText = '- Less';
    $(".more-less .more-block").each(function(){
        if ($(this).height() > adjustheight){
            $(this).css('height', adjustheight).css('overflow', 'hidden');
            $(this).parent(".more-less").append
                ('<a style="cursor:pointer" class="adjust">' + moreText + '</a>');
        }
    });
    $(".adjust").click(function() {
        if ($(this).prev().css('overflow') == 'hidden')
        {
            $(this).prev().css('height', 'auto').css('overflow', 'visible');
            $(this).text(lessText);
        }
        else {
            $(this).prev().css('height', adjustheight).css('overflow', 'hidden');
            $(this).text(moreText);
        }
    });
});

基于此,但已更新:http://shakenandstirredweb.com/240/jquery-moreless-text

答案 14 :(得分:0)

使用此代码,如果元素的高度受到最大高度样式的限制,则不需要额外的包装div。

// Shorten texts in overflowed paragraphs to emulate Operas text-overflow: -o-ellipsis-lastline
$('.ellipsis-lastline').each(function(i, e) {
    var $e = $(e), original_content = $e.text();
    while (e.scrollHeight > e.clientHeight)
        $e.text($e.text().replace(/\W*\w+\W*$/, '…'));
    $e.attr('data-original-content', original_content);
});

它还将原始文本保存在只能使用样式显示的数据属性中,例如。鼠标悬停:

.ellipsis-lastline {
    max-height: 5em;
}
.ellipsis-lastline:before {
    content: attr(data-original-content);
    position: absolute;
    display: none;
}
.ellipsis-lastline:hover:before {
    display: block;
}

答案 15 :(得分:0)

在我的场景中,我无法使用上面提到的任何功能,我还需要告诉函数显示多少行,而不管字体大小或容器大小。

我的解决方案基于使用Canvas.measureText方法(这是一个HTML5功能),正如here Domi所解释的那样,所以它并非完全交叉浏览器。

您可以在this fiddle上查看其工作原理。

这是代码:

var processTexts = function processTexts($dom) {
    var canvas = processTexts .canvas || (processTexts .canvas = document.createElement("canvas"));

    $dom.find('.block-with-ellipsis').each(function (idx, ctrl) {
        var currentLineAdded = false;
        var $this = $(ctrl);

        var font = $this.css('font-family').split(",")[0]; //This worked for me so far, but it is not always so easy.
        var fontWeight = $(this).css('font-weight');
        var fontSize = $(this).css('font-size');
        var fullFont = fontWeight + " " + fontSize + " " + font;
        // re-use canvas object for better performance
        var context = canvas.getContext("2d");
        context.font = fullFont;

        var widthOfContainer = $this.width();
        var text = $.trim(ctrl.innerHTML);
        var words = text.split(" ");
        var lines = [];
        //Number of lines to span over, this could be calculated/obtained some other way.
        var lineCount = $this.data('line-count');

        var currentLine = words[0];
        var processing = "";

        var isProcessing = true;
        var metrics = context.measureText(text);
        var processingWidth = metrics.width;
        if (processingWidth > widthOfContainer) {
            for (var i = 1; i < words.length && isProcessing; i++) {
                currentLineAdded = false;
                processing = currentLine + " " + words[i];
                metrics = context.measureText(processing);
                processingWidth = metrics.width;
                if (processingWidth <= widthOfContainer) {
                    currentLine = processing;
                } else {
                    if (lines.length < lineCount - 1) {
                        lines.push(currentLine);
                        currentLine = words[i];
                        currentLineAdded = true;
                    } else {
                        processing = currentLine + "...";
                        metrics = context.measureText(processing);
                        processingWidth = metrics.width;
                        if (processingWidth <= widthOfContainer) {
                            currentLine = processing;
                        } else {
                            currentLine = currentLine.slice(0, -3) + "...";
                        }
                        lines.push(currentLine);
                        isProcessing = false;
                        currentLineAdded = true;
                    }
                }
            }
            if (!currentLineAdded)
                lines.push(currentLine);
            ctrl.innerHTML = lines.join(" ");
        }
    });
};

(function () {
    $(document).ready(function () {
        processTexts($(document));
    });
})();

使用它的HTML将是这样的:

<div class="block-with-ellipsis" data-line-count="2">
    VERY LONG TEXT THAT I WANT TO BREAK IN LINES. VERY LONG TEXT THAT I WANT TO BREAK IN LINES.
</div>

获取font-family的代码非常简单,在我的情况下可行,但对于更复杂的场景,您可能需要使用these lines内的内容。

此外,在我的情况下,我告诉函数要使用多少行,但您可以根据容器大小和字体计算要显示的行数。

答案 16 :(得分:0)

这里我用更快的算法创建了另一个库。请检查:

https://github.com/i-ahmed-biz/fast-ellipsis

使用凉亭安装:

bower install fast-ellipsis

使用npm安装:

npm install fast-ellipsis 

希望你喜欢!

答案 17 :(得分:0)

扩展@DanMan的解决方案:在使用可变宽度字体的情况下,您可以使用平均字体宽度。这有两个问题:1)有太多W的文本会溢出,2)有太多我的文本会被先前截断。

或者您可以采用最坏情况的方法并使用字母“W”的宽度(我相信这是最宽的)。这消除了上述问题1,但加剧了问题2.

另一种方法可能是:在div中留下overflow: clip并添加带有float: right; position: relative; bottom: 0px;(未经测试)的省略号部分(可能是另一个div或图像)。诀窍是让图像显示在文本末尾之上。

您也可以只在知道图像溢出时显示图像(例如,大约100个字符后)

答案 18 :(得分:0)

非常简单的javascript解决方案。 Div的样式必须为:

.croppedTexts { 
  max-height: 32px;
  overflow: hidden;
}

和JS:

var list = document.body.getElementsByClassName("croppedTexts");
for (var i = 0; i < list.length; i++) {
  cropTextToFit(list[i]);
}

function cropTextToFit (o) {
  var lastIndex;
  var txt = o.innerHTML;
  if (!o.title) o.title = txt;

  while (o.scrollHeight > o.clientHeight) {
    lastIndex = txt.lastIndexOf(" ");
    if (lastIndex == -1) return;
    txt = txt.substring(0, lastIndex);
    o.innerHTML = txt + "…";
  }
}

答案 19 :(得分:0)

我制作了一个保留html不变的版本。 jsfiddle example

jQuery

function shorten_text_to_parent_size(text_elem) {
  textContainerHeight = text_elem.parent().height();


  while (text_elem.outerHeight(true) > textContainerHeight) {
    text_elem.html(function (index, text) {
      return text.replace(/(?!(<[^>]*>))\W*\s(\S)*$/, '...');
    });

  }
}

$('.ellipsis_multiline').each(function () {
  shorten_text_to_parent_size($(this))
});

CSS

.ellipsis_multiline_box {
  position: relative;
  overflow-y: hidden;
  text-overflow: ellipsis;
}

jsfiddle example

答案 20 :(得分:0)

我写了一个解决这个问题的角度组件。它将给定的文本拆分为span元素。渲染后,它将删除所有溢出元素,并将省略号放在最后一个可见元素之后。

用法示例:

<app-text-overflow-ellipsis [text]="someText" style="max-height: 50px"></app-text-overflow-ellipsis>

Stackblitz演示:https://stackblitz.com/edit/angular-wfdqtd

组件:

import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef, HostListener,
  Input,
  OnChanges,
  ViewChild
} from '@angular/core';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-text-overflow-ellipsis',
  template: `
    <span *ngFor="let word of words; let i = index" [innerHTML]="word + (!endsWithHyphen(i) ? ' ' : '')"></span>
    <span #ellipsis [hidden]="!showEllipsis && !initializing" [class.initializing]="initializing" [innerHTML]="'...' + (initializing ? '&nbsp;' : '')"></span>
  `,
  styles: [`
    :host {
      display: block; 
      position: relative;
    }
    .initializing {
      opacity: 0;
    }
  `
  ]
})

export class TextOverflowEllipsisComponent implements OnChanges {
  @Input()
  text: string;

  showEllipsis: boolean;
  initializing: boolean;

  words: string[];

  @ViewChild('ellipsis')
  ellipsisElement: ElementRef;

  constructor(private element: ElementRef, private cdRef: ChangeDetectorRef) {}

  ngOnChanges(){
    this.init();
  }

  @HostListener('window:resize')
  init(){
    // add space after hyphens
    let text = this.text.replace(/-/g, '- ') ;

    this.words = text.split(' ');
    this.initializing = true;
    this.showEllipsis = false;
    this.cdRef.detectChanges();

    setTimeout(() => {
      this.initializing = false;
      let containerElement = this.element.nativeElement;
      let containerWidth = containerElement.clientWidth;
      let wordElements = (<HTMLElement[]>Array.from(containerElement.childNodes)).filter((element) =>
        element.getBoundingClientRect && element !== this.ellipsisElement.nativeElement
      );
      let lines = this.getLines(wordElements, containerWidth);
      let indexOfLastLine = lines.length - 1;
      let lineHeight = this.deductLineHeight(lines);
      if (!lineHeight) {
        return;
      }
      let indexOfLastVisibleLine = Math.floor(containerElement.clientHeight / lineHeight) - 1;

      if (indexOfLastVisibleLine < indexOfLastLine) {

        // remove overflowing lines
        for (let i = indexOfLastLine; i > indexOfLastVisibleLine; i--) {
          for (let j = 0; j < lines[i].length; j++) {
            this.words.splice(-1, 1);
          }
        }

        // make ellipsis fit into last visible line
        let lastVisibleLine = lines[indexOfLastVisibleLine];
        let indexOfLastWord = lastVisibleLine.length - 1;
        let lastVisibleLineWidth = lastVisibleLine.map(
          (element) => element.getBoundingClientRect().width
        ).reduce(
          (width, sum) => width + sum, 0
        );
        let ellipsisWidth = this.ellipsisElement.nativeElement.getBoundingClientRect().width;
        for (let i = indexOfLastWord; lastVisibleLineWidth + ellipsisWidth >= containerWidth; i--) {
          let wordWidth = lastVisibleLine[i].getBoundingClientRect().width;
          lastVisibleLineWidth -= wordWidth;
          this.words.splice(-1, 1);
        }


        this.showEllipsis = true;
      }
      this.cdRef.detectChanges();

      // delay is to prevent from font loading issues
    }, 1000);

  }

  deductLineHeight(lines: HTMLElement[][]): number {
    try {
      let rect0 = lines[0][0].getBoundingClientRect();
      let y0 = rect0['y'] || rect0['top'] || 0;
      let rect1 = lines[1][0].getBoundingClientRect();
      let y1 = rect1['y'] || rect1['top'] || 0;
      let lineHeight = y1 - y0;
      if (lineHeight > 0){
        return lineHeight;
      }
    } catch (e) {}

    return null;
  }

  getLines(nodes: HTMLElement[], clientWidth: number): HTMLElement[][] {
    let lines = [];
    let currentLine = [];
    let currentLineWidth = 0;

    nodes.forEach((node) => {
      if (!node.getBoundingClientRect){
        return;
      }

      let nodeWidth = node.getBoundingClientRect().width;
      if (currentLineWidth + nodeWidth > clientWidth){
        lines.push(currentLine);
        currentLine = [];
        currentLineWidth = 0;
      }
      currentLine.push(node);
      currentLineWidth += nodeWidth;
    });
    lines.push(currentLine);

    return lines;
  }

  endsWithHyphen(index: number): boolean {
    let length = this.words[index].length;
    return this.words[index][length - 1] === '-' && this.words[index + 1] && this.words[index + 1][0];
  }
}

答案 21 :(得分:0)

如果没有像Courier这样的固定宽度字体,你可能无法做到(目前?)。对于固定宽度的字体,每个字母占据相同的水平空间,因此您可以计算字母数,并将结果乘以ems或exs中的当前字体大小。然后你只需要测试一行上有多少个字母,然后将它分解。

或者,对于非固定字体,您可以为所有可能的字符(例如i = 2px,m = 5px)创建映射,然后进行数学运算。虽然很多丑陋的工作。

答案 22 :(得分:-1)

您可以将-webkit-line-clamp属性与div一起使用。

div {
  width: 205px;
  height: 40px;
  background-color: gainsboro;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
<div>This is a multi-lines text block, some lines inside the div, while some outside</div>

答案 23 :(得分:-2)

不确定这是否是您正在寻找的,它使用最小高度而不是高度。

    <div id="content" style="min-height:10px;width:190px;background:lightblue;">
    <?php 
        function truncate($text,$numb) {
            // source: www.kigoobe.com, please keep this if you are using the function
            $text = html_entity_decode($text, ENT_QUOTES);
            if (strlen($text) > $numb) {
                $text = substr($text, 0, $numb);
                $etc = "..."; 
                $text = $text.$etc;
            } 
            $text = htmlentities($text, ENT_QUOTES);
            return $text;
        }
        echo truncate("this is a multi-lines text block, some lines inside the div, while some outside", 63);
    ?>
    </div>

答案 24 :(得分:-2)

非常简单的func会做。

指令:

  $scope.truncateAlbumName = function (name) {
    if (name.length > 36) {
      return name.slice(0, 34) + "..";
    } else {
      return name;
    }
  };

查看:

<#p>{{truncateAlbumName(album.name)}}<#/p>