Кросс-браузерное многострочное переполнение текста с добавлением многоточия в пределах фиксированной ширины и высоты

178

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

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

текст переполнения

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

Эдвард
источник
1
и stackoverflow.com/questions/3922739/… для решения только для css
Евгений
статья по теме css-tricks.com/line-clampin
Adrien Be
2
Для тех, кто ищет это в середине 2016 года, короткий ответ: НЕТ, это невозможно в элегантном, кросс-браузерном, CSS-стиле. Решение, которое часто дается как наиболее близкое к завершению ( codepen.io/romanrudenko/pen/ymHFh ), является настолько голдбергским, что причиняет боль всему вашему телу, и все же довольно уродливо
Конрад

Ответы:

91

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

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

<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 .

капа
источник
1
отличное решение @bazmegakapa ... но у меня есть некоторые проблемы, пытаясь адаптировать его к моему делу. У меня есть разные liи внутри каждого из них есть .blockи, .block h2и мне нужно применить это к h2внутренней части, .blockно я не мог заставить его работать. Есть ли разница, если их больше одного .block h2?
Алекс
1
В моем случае оставалось только 2 строки текста, когда их должно было быть 3. Очевидно, мой контейнер был меньше, чем строка height*3на несколько пикселей. Легко исправить, просто добавив несколько пикселей кdivh
Lukas LT
3
У меня был плохой опыт бесконечного цикла с этим сценарием, потому что текст содержал только одно очень длинное слово, поэтому регулярное выражение замены никогда не совпадало. Чтобы избежать этого, добавьте этот код сразу после whileстроки:if(!$p.text().match(/\W*\s(\S)*$/)) break;
KrisWebDev
1
Хотя вряд ли это будет проблемой в этом случае, обновление DOM и повторная проверка макета является плохой идеей, потому что это может привести к замедлению. Чтобы смягчить это, вы можете сделать что-то похожее на бинарный поиск: проверить, подходит ли текстовый блок, иначе разделить текст на слова или символы и определить границы (нижний = 1 слово / символы, верхний = все слова / символы) , while ((upper-lower)>1) {let middle=((lower+upper)/2)|0 /*|0 is quick floor*/; if (test(words.slice(0,middle)+'...')) {lower=middle;} else {upper=middle;}}. Как обнаружил @KrisWebDev, вы также захотите проверить одно гигантское слово.
Чиното Вокро
1
Это решение отлично. В моем случае мне нужно отслеживать исходный текст, чтобы я мог быстро урезать полное текстовое значение. Поэтому, когда страница загружается, я сохраняю исходный текст в переменной, и перед запуском этой логики я обязательно «обновляю» элемент исходным текстовым значением. Добавьте debounce, и он прекрасно работает.
besseddrest
68

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

$(".ellipsis").dotdotdot();
Matt
источник
11
Как бы ты это произнес? точка-точка-точка-точка?
JackAce
58
Действительно отстой, чтобы использовать> 600 строк js для решения проблемы, которая должна быть решена css
Jethro Larson
Я попробовал, и он работает нормально. Должен быть принятый ответ
AbdelHady
1
Работает, но обязательно используйте событие window.loaded, а не $ (document) .ready (), так как шрифты и другие внешние ресурсы могут повлиять на макет вашего HTML. Если dotdotdot выполняется до загрузки этих ресурсов, текст не будет усечен в правильной позиции.
sboisse
10
Это коммерческий инструмент, он стоит 5 долларов за один сайт и 35 долларов за несколько сайтов. Лицензирование было бы болью. Я думал, что это бесплатно и сразу же интегрируется, не так!
ген б.
29

Библиотеки 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 "читать дальше" решение

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

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

В HTML нет такой возможности, и это очень расстраивает.

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

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

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

Сэмюэл Россиль
источник
"ошибка при установлении соединения с БД" ... вы можете захотеть поступить так же, как и все остальные, и разместить свой проект на Github, возможно, это будет лучше для вас и для сообщества :)
Adrien Be
@AdrienBe это на Github: github.com/rossille/jstext , и вы правы, поскольку github более стабилен, чем мой сайт, я установил страницу github в качестве главной ссылки
Сэмюэль Россиль
@SamuelRossille отличные новости, спасибо за быстрое обновление!
Адриен Бе
4

Чистое решение 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);
prashn64
источник
Это очень неэффективный код. Кстати, использование «while» - это потенциальная ошибка с бесконечным циклом.
WebBrother
4

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

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

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

Мириам Зальцер
источник
Пожалуйста, поделитесь важными частями вашего решения, так что только ссылки не допускаются.
Капа
блестящий аспект этого заключается в том, что сам текст не усекается, поэтому, если пользователь копирует и вставляет таблицу, появляется весь контент.
прототип
Очень хорошая концепция. Это также упоминается в этой статье («исчезновение»). Я считаю, что css-tricks.com/line-clampin
Adrien Be
4

Вот простой способ CSS сделать это: http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/

Вот резюме:

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

<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>
Аарон Хоффман
источник
4

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

-webkit-line-clamp: <integer>это означает установить максимальное количество строк перед усечением содержимого, а затем отобразить многоточие (…)в конце последней строки.

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

Пенни лю
источник
2
Не уверен, почему кто-то отклонил этот ответ. Поддержка браузера по состоянию на март 2020 года довольно приличная - 95% caniuse.com/#search=line-clamp
Юлиан
2

Вот ванильное решение 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>

Вы можете поиграть с ним в коде ниже. Попробуйте изменить размер шрифта на панели CSS и внесите незначительные изменения в панель HTML (например, добавить дополнительное место где-нибудь), чтобы обновить результаты. Независимо от размера шрифта средний абзац всегда должен быть обрезан до количества строк во втором параметре, переданном в limitLines ().

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

thdoan
источник
2

РЕДАКТИРОВАТЬ: наткнулся на Shave, который является плагином JS, который действительно хорошо выполняет многострочное усечение текста на основе заданной максимальной высоты. Он использует бинарный поиск, чтобы найти оптимальную точку останова. Определенно стоит исследовать.


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

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

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

  1. Рассчитать среднюю ширину символа варианта шрифта для желаемого размера шрифта.
  2. Рассчитать ширину контейнера
  3. Рассчитать количество символов, которые помещаются на одной строке в контейнере
  4. Вычислите количество символов для усечения строки на основе количества символов, которые помещаются в строку, и количества строк, которые текст должен обернуть.
  5. Усечение входного текста на основе предыдущего расчета (с учетом дополнительных символов, добавленных многоточием) и добавление «...» в конец

Пример кода:

/**
 * 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. Добавлю ссылки и ссылки на оригинальные посты после того, как верну ноутбук (нужна история)

PPS: Просто понял, что это очень похоже на подход, предложенный @DanMan и @ st.never. Извлеките фрагменты кода для примера реализации.

Чираг Равиндра
источник
2

Очень простое решение для 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 + "…";
  }
}
Михал Политцер
источник
1

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

Пример больше скриншота

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

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

Когда добавлена ​​следующая функция jQuery, любой из div, который больше значения регулировки высоты, будет усечен и будет добавлена ​​ссылка «Дополнительно».

$(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

Энди Беверли
источник
<вздох> Я думал, что кто-то может понизить это, возможно потому, что это не точный ответ на вопрос. Тем не менее, я надеюсь, что кто-то найдет это полезным, так как я не мог получить эту информацию где-либо еще, и на этом я закончил поиск.
Энди Беверли
1

Упомянутый плагин 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>
Эдвард Оламисан
источник
1

Чистая демонстрация 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);
WebBrother
источник
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 с небольшим затуханием в конце

Fire Fox

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

Хром

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

Алексис Дюран
источник
1

Нашел это короткое CSS- решение в ответе Adrien Be :

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

По состоянию на март 2020 года поддержка браузеров составляет 95,3% , не поддерживается в IE и Opera Mini. Работает на Chrome, Safari, Firefox и Edge.

Юлиан
источник
0

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

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

DanMan
источник
0

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

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

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

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

st.never
источник
Что такое overflow: clip? И что вы ожидаете от CSS float?
Капа
0

С этим кодом нет необходимости в дополнительном элементе-обёртке, если высота элемента ограничена стилем 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;
}
Johan
источник
1
Это часто бесконечный цикл.
Атадж
0

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

Я основано мое решение по использованию Canvas.measureText метода (whic является HTML5 функция) , как описано здесь , на Доми , так что это не полностью кросс-браузер.

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

Это код:

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>

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

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

Dzyann
источник
0

Я сделал версию, которая оставляет 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

Вот это да
источник
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];
  }
}
Мартин Кремер
источник
0

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

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

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

bower install fast-ellipsis

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

npm install fast-ellipsis 

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

И. Ахмед
источник
-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>
khairil
источник
4
Проблема в том, что число 63 в ваших кодах. Если число известно, то все становится легко, просто функция усечения выполняет эту работу, как ваши коды. Однако как узнать номер? Другими словами, как узнать, где текст будет разбит на строки? Если на этот вопрос можно ответить, то проблема может быть просто решена в логике «1, вычисли число; 2, обрежь»
Эдвард
-3

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

Директива:

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

Посмотреть:

<#p>{{truncateAlbumName(album.name)}}<#/p>
yamenator
источник
3
Как мы уже обсуждали в других ответах, проблема в вашем коде - это число 36. Кроме того, что ваш код специфичен для определенной ширины контейнера, он также не точен: при использовании шрифтов без фиксированной ширины между буквами могут быть большие различия , См. iiiiiiiiiiПротив MMMMMMMMMM(хотя текущий шрифт не так видим, хотя: D).
Капа