Кросс-браузерный многострочный переполнение текста с многоточием, добавленным с фиксированной шириной и высотой '<div>'

Я сделал образ для этого вопроса, чтобы его было легче понять.

Можно ли создать многоточие на <div> с фиксированной шириной и несколькими строками?

text-overflow

Я пробовал некоторые jQuery-плагины тут и там, но не могу найти тот, который ищу. Любая рекомендация? Идеи?

Ответ 1

Просто быстрая базовая идея.

Я тестировал следующую разметку:

<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 результат остается "визуально правильным" (без "...", конечно).

Если вы комбинируете это с разумным усечением на стороне сервера (что оставляет только небольшие накладные расходы), то он будет работать быстрее:).

Опять же, это не полное решение, просто идея.

UPDATE: Добавлен jsFiddle Demo.

Ответ 2

Попробуйте плагин jQuery.dotdotdot.

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

Ответ 3

Библиотеки Javascript для "зажима строк"

Обратите внимание, что "зажим линии" также обозначается как "Многоточие на блоке из нескольких линий" или "Вертикальный многоточие".


github.com/BeSite/jQuery.dotdotdot


github.com/josephschmitt/Clamp.js


Вот еще несколько, которые я еще не расследовал:


Решения CSS для зажима линий

Существует несколько CSS-решений, но простейший использует -webkit-line-clamp, который плохо поддерживает браузер. Смотрите живую демонстрацию на jsfiddle.net/AdrienBe/jthu55v7/

Многие люди приложили огромные усилия, чтобы сделать это, используя только CSS. Смотрите статьи и вопросы об этом:


Что я бы порекомендовал

Будь проще. Если у вас нет достаточно времени, чтобы посвятить эту функцию, перейдите на простую & проверенное решение: простой CSS или хорошо протестированная библиотека javascript.

Пойдите для чего-то необычного/сложного/высоко настроенного & Вы заплатите цену за это в будущем.


Что делают другие

Хорошим решением может стать постепенное исчезновение, как у Airbnb. Это, вероятно, базовый CSS в сочетании с базовым jQuery. На самом деле, это похоже на это решение на CSSTricks

AirBnb "read more" solution

О, и если вы ищете вдохновение для дизайна:

  • smashingmagazine.com/2009/07/designing-read-more-and-continue-reading-links/, хотя с 2009 года...
  • Dribbble, вероятно, имеет интересные дизайны... Хотя я не мог найти способ собрать их (через поиск или теги), не стесняйтесь поделиться соответствующей ссылкой

Ответ 4

В HTML нет такой функции, и это очень расстраивает.

Я разработал библиотеку, чтобы справиться с этим.

  • Многострочный переполнение текста: эллипсис
  • Многострочный текст с технологиями, которые его не поддерживают: SVG, Canvas например
  • Имеют точно такие же разрывы строк в вашем тексте SVG, в вашем рендеринге HTML и в вашем экспорте PDF, например

Отметьте мой сайт для скриншота, учебника и ссылки dowload.

Ответ 5

Решение Pure JS на основе решения bažmegakapa и некоторая очистка для учета людей, которые пытаются дать высоту/максимальную высоту, которая меньше, чем элемент 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);

Ответ 6

У меня есть решение, которое работает хорошо, но вместо этого эллипсис использует градиент. Преимущества заключаются в том, что вам не нужно выполнять какие-либо вычисления JavaScript, и он работает для контейнеров с переменной шириной, включая ячейки таблицы. Он использует пару дополнительных div, но очень легко реализовать.

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

Изменить: Извините, я не знал, что ссылки недостаточно. Решение состоит в том, чтобы поместить div вокруг текста и стилизовать div для управления переполнением. Внутри div помещается еще один div с градиентом "fade" , который можно сделать с помощью CSS или изображения (для старого IE). Градиент переходит от прозрачного к цвету фона ячейки таблицы и немного шире, чем многоточие. Если текст длинный и переполненный, он попадает под "fade" div и выглядит "потушен". Если текст невелик, затухание невидимо, поэтому проблем нет. Два контейнера можно отрегулировать, чтобы показать одну или несколько строк, установив высоту контейнера как кратную высоте текстовой строки. Разделение "fade" может быть расположено только для закрытия последней строки.

Ответ 7

Вот чистый способ 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>

Ответ 8

Здесь ванильное JavaScript-решение, которое вы можете использовать в крайнем случае:

// @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);
#test {
  width: 320px;
  font-size: 18px;
}
<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>

Ответ 9

Не точный ответ на вопрос, но я натолкнулся на эту страницу, пытаясь сделать очень похожее, но хочу добавить ссылку на "больше", а не просто на эллипсис. Это функция jQuery, которая добавит ссылку "больше" к тексту, который переполняет контейнер. Лично я использую это с Bootstrap, но, конечно, это будет работать без.

Example more screenshot

Чтобы использовать, поместите текст в контейнер следующим образом:

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

Когда добавляется следующая функция jQuery, любая из div, которая больше, чем значение adjustheight, будет усечена и добавлена ​​ссылка "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

Ответ 10

Указанный dotdotdot плагин jQuery работает с 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>

Ответ 11

Чистая демонстрация JS (без цикла jQuery и 'while')

Когда я искал решение проблемы многострочной эллипсис, я был удивлен, что нет никакого хорошего без jQuery. Также есть несколько решений, основанных на цикле "while", но я думаю, что они не эффективны и опасны из-за возможности попасть в бесконечный цикл. Поэтому я написал этот код:

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

EDIT: Вышел через Shave, который является JS-плагином, который очень хорошо выполняет многострочное усечение строк на основе заданной максимальной высоты. Он использует бинарный поиск, чтобы найти оптимальную точку останова. Определенно стоит исследовать.


ОРИГИНАЛЬНЫЙ ОТВЕТ:

Мне пришлось придумать решение VanS для решения этой проблемы. В том случае, когда я работал, мне пришлось поместить длинное название продукта в ограниченную ширину и более двух строк; при необходимости усеченный эллипсисом.

Я использовал ответы от различных сообщений SO, чтобы приготовить что-то, что соответствует моим потребностям. Стратегия такова:

  • Рассчитайте среднюю ширину символа шрифта для желаемого размера шрифта.
  • Вычислить ширину контейнера
  • Рассчитать количество символов, которые помещаются на одной строке в контейнере
  • Вычислить количество символов для усечения строки в зависимости от количества символов, которые вписываются в строку, и количества строк, которые текст должен обернуть.
  • Усечь входной текст на основе предыдущего вычисления (факторинг для дополнительных символов, добавленных эллипсисом) и добавить "..." в конец

Пример кода:

/**
 * 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 = "[email protected]#$%^&*()";
    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>

Ответ 13

Возможно, довольно поздно, но с помощью 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-строчная высота. Ожидаемый результат: хром для рендеринга с ... в конце и FF с некоторой потерей в конце

Firefox

введите описание изображения здесь

Chrome

введите описание изображения здесь

Ответ 14

Очень простое решение для JavaScript. Divs должен быть стилизован, например:

.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 + "…";
  }
}

Ответ 15

Вероятно, вы не можете сделать это (в настоящее время?) без шрифта с фиксированной шириной, такого как Courier. С шрифтом фиксированной ширины каждая буква занимает одно и то же горизонтальное пространство, поэтому вы можете, вероятно, подсчитать буквы и умножить результат на текущий размер шрифта в ems или exs. Тогда вам просто нужно будет проверить, сколько букв вписывается в одну строку, а затем разбить ее.

В качестве альтернативы для нефиксированных шрифтов вы можете создать сопоставление для всех возможных символов (например, я = 2px, m = 5px), а затем выполнить математику. Много уродливых работ, хотя.

Ответ 16

Чтобы расширить решение @DanMan: в случае, когда используются шрифты переменной ширины, вы можете использовать среднюю ширину шрифта. Это имеет две проблемы: 1) текст со слишком большим количеством W будет переполняться и 2) текст со слишком большим количеством я бы урезал раньше.

Или вы можете использовать наихудший подход и использовать ширину буквы "W" (которая, по моему мнению, является самой широкой). Это устраняет проблему 1 выше, но усиливает проблему 2.

Другой подход может заключаться в следующем: оставьте overflow: clip в div и добавьте раздел многоточия (возможно, другой div или изображение) с float: right; position: relative; bottom: 0px; (untested). Хитрость заключается в том, чтобы изображение появилось над концом текста.

Вы также можете отображать только изображение, когда знаете, что оно переполняется (скажем, примерно через 100 символов)

Ответ 17

С помощью этого кода нет необходимости в дополнительном div div оболочки, если элемент имеет высоту, ограниченную стилем max-height.

// 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;
}

Ответ 18

В моем сценарии я не мог работать над любыми функциями, упомянутыми выше, и мне также нужно было сказать функции, сколько строк должно отображаться независимо от размера шрифта или размера контейнера.

Я основывал свое решение на использовании метода Canvas.measureText (который является HTML5), как описано здесь Domi, поэтому он не является полностью перекрестным браузером.

Вы можете увидеть, как он работает на эту скрипту.

Это код:

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>

Код для получения семейства шрифтов довольно прост и в моем случае работает, но для более сложных сценариев вам может понадобиться что-то использовать в этих строках.

Кроме того, в моем случае я рассказываю функции, сколько строк использовать, но вы могли бы рассчитать, сколько строк должно отображаться в соответствии с размером и шрифтом контейнера.

Ответ 19

Здесь я сделал еще одну библиотеку с более быстрым алгоритмом. Пожалуйста, проверьте:

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

Чтобы установить с помощью Bower:

bower install fast-ellipsis

Для установки с помощью npm:

npm install fast-ellipsis 

Надеюсь, вам понравится!

Ответ 20

Я сделал версию, которая оставляет HTML нетронутым. пример jsfiddle

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

Ответ 21

Я написал angular компонент, который решает проблему. Он разбивает данный текст на элементы 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];
  }
}

Ответ 22

Вы можете использовать свойство -webkit-line-clamp с div.

div {
  width: 205px;
  height: 40px;
  display: -webkit-box;
  overflow: hidden;
  background-color: silver;
  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

Не уверен, что это то, что вы ищете, оно использует min-height вместо высоты.

    <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

Очень простая функция.

Директива

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

Вид:

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