Desbordamiento de texto de varias líneas en varios navegadores con puntos suspensivos agregados dentro de un ancho y una altura fijos


178

Hice una imagen para esta pregunta para que sea más fácil de entender.

¿Es posible crear una elipsis en un <div>ancho fijo y varias líneas?

desbordamiento de texto

He probado algunos complementos de jQuery aquí y allá, pero no puedo encontrar el que estoy buscando. ¿Alguna recomendacion? Ideas?



1
y stackoverflow.com/questions/3922739/… para una solución de solo CSS
Evgeny

artículo relacionado css-tricks.com/line-clampin
Adrien Be

2
Para cualquiera que esté buscando esto a mediados de 2016, la respuesta corta es: NO, no es posible de manera elegante, entre navegadores, solo CSS. La solución que a menudo se da como la más cercana a completarse ( codepen.io/romanrudenko/pen/ymHFh ) es tan Goldbergian que hace que todo tu cuerpo duela, y sigue siendo bastante feo.
konrad

Respuestas:


91

Solo una idea básica rápida.

Estaba probando con el siguiente marcado:

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

Y CSS:

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

Aplicando este jQuery logrará el resultado deseado:

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

Intenta eliminar repetidamente la última palabra del texto hasta que alcanza el tamaño deseado. Debido al desbordamiento: oculto; el proceso permanece invisible e incluso con JS apagado, el resultado sigue siendo 'visualmente correcto' (sin el "...", por supuesto).

Si combina esto con un truncamiento sensible en el lado del servidor (que deja solo una pequeña sobrecarga), se ejecutará más rápido :).

Nuevamente, esta no es una solución completa, solo una idea.

ACTUALIZACIÓN: se agregó una demostración jsFiddle .


1
Gran solución @bazmegakapa ... pero tengo algunos problemas tratando de adaptarlo a mi caso. Tengo varios liy dentro de cada uno hay .blockun .block h2y ay necesito aplicar esto al h2interior, .blockpero no pude hacerlo funcionar. ¿Es diferente si hay más de uno .block h2?
Alex

1
En mi caso, dejaba solo 2 líneas de texto cuando debería haber habido 3. Aparentemente, mi contenedor era más pequeño que la línea height*3por unos pocos píxeles. La solución fácil es simplemente agregar algunos píxeles adivh
Lukas LT

3
Tuve una mala experiencia de bucle infinito con este script porque el texto contenía solo una palabra muy larga, por lo que el regexp de reemplazo nunca coincidió. Para evitar esto, agregue este código justo después de la whilelínea:if(!$p.text().match(/\W*\s(\S)*$/)) break;
KrisWebDev

1
Si bien no es probable que sea un problema en este caso, actualizar el DOM y verificar el diseño repetidamente es una mala idea porque podría causar una desaceleración. Para mitigar esto, podría hacer algo similar a una búsqueda binaria: pruebe para ver si el bloque de texto ya encaja, de lo contrario, divida el texto en palabras o caracteres y defina sus límites (inferior = 1 palabra / caracteres, superior = todas las palabras / caracteres) , 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;}}. Como encontró @KrisWebDev, también querrá verificar si hay una palabra gigante.
Chinoto Vokro

1
Esta solución es genial. En mi caso, necesito hacer un seguimiento del texto original para poder truncar el valor del texto completo de manera receptiva. Entonces, cuando se carga la página, almaceno el texto original en una variable, y antes de ejecutar esta lógica me aseguro de 'actualizar' el elemento con el valor del texto original. Agregue debounce, y funciona de maravilla.
besseddrest

68

Pruebe el complemento jQuery.dotdotdot .

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

11
¿Cómo pronunciarías eso? punto-punto-punto-punto?
JackAce

58
Realmente apesta usar> 600 líneas de js para resolver un problema que debería ser resuelto por css
Jethro Larson

Lo he intentado y funciona bien. Debería ser la respuesta aceptada
AbdelHady

1
Funciona, pero asegúrese de usar el evento window.loaded y no $ (document) .ready (), ya que las fuentes y otros recursos externos pueden afectar el diseño de su HTML. Si dotdotdot se ejecuta antes de cargar estos recursos, el texto no se truncará en la posición correcta.
sboisse

10
Esta es una herramienta comercial, cuesta $ 5 para un solo sitio y $ 35 para múltiples sitios. La licencia sería un dolor. Pensé que era gratis e inmediatamente integrable, ¡no es así!
gen b.

29

Bibliotecas Javascript para "sujeción de línea"

Tenga en cuenta que "sujeción de línea" también se conoce como "puntos suspensivos en el bloque de líneas múltiples" o "puntos suspensivos verticales".


github.com/BeSite/jQuery.dotdotdot


github.com/josephschmitt/Clamp.js


Aquí hay algunos más que aún no investigué:


Soluciones CSS para sujeción de línea

Hay algunas soluciones CSS, pero los usos más simples -webkit-line-clampque tienen poca compatibilidad con el navegador . Ver demostración en vivo en jsfiddle.net/AdrienBe/jthu55v7/

Muchas personas hicieron grandes esfuerzos para que esto suceda utilizando solo CSS. Ver artículos y preguntas al respecto:


Lo que recomiendo

Mantenlo simple. A menos que tenga una gran cantidad de tiempo para dedicar a esta función, busque la solución más simple y probada: CSS simple o una biblioteca de JavaScript bien probada.

Ve por algo elegante / complejo / altamente personalizado y pagarás el precio por esto en el futuro.


Lo que otros hacen

Tener un desvanecimiento como lo hace Airbnb podría ser una buena solución. Probablemente sea CSS básico junto con jQuery básico. En realidad, parece muy similar a esta solución en CSSTricks

Solución "leer más" de AirBnb

Ah, y si buscas inspiraciones de diseño:


6

No existe tal característica en HTML, y esto es muy frustrante.

He desarrollado una biblioteca para lidiar con esto.

  • Desbordamiento de texto multilínea: puntos suspensivos
  • Texto multilínea con tecnologías que no lo admiten: SVG, Canvas por ejemplo
  • Tenga exactamente los mismos saltos de línea en su texto SVG, en su representación HTML y en su exportación de PDF, por ejemplo

Mira mi sitio para ver capturas de pantalla, tutoriales y enlaces de descarga.


"error al establecer conexión db" ... puede querer hacer como todos los demás y alojar su proyecto en Github, probablemente sería mejor para usted y para la comunidad :)
Adrien Be

@AdrienBe está en Github: github.com/rossille/jstext , y tienes razón, siendo github más estable que mi sitio web, configuré la página de github como el enlace principal
Samuel Rossille

@SamuelRossille buenas noticias, ¡gracias por la rápida actualización!
Adrien Be

4

Solución JS pura basada en la solución de bažmegakapa, y algunas limpiezas para tener en cuenta a las personas que intentan dar una altura / altura máxima que es menor que la línea del elemento

  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);

Este es un código muy poco efectivo. Por cierto, el uso del aspecto "while" es un error potencial con bucle infinito.
WebBrother

4

Tengo una solución que funciona bien, pero en lugar de puntos suspensivos, usa un gradiente. Las ventajas son que no tiene que hacer ningún cálculo de JavaScript y funciona para contenedores de ancho variable, incluidas las celdas de la tabla. Utiliza un par de divs adicionales, pero es muy fácil de implementar.

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

Editar: Lo siento, no sabía que el enlace no era suficiente. La solución es poner un div alrededor del texto y diseñar el div para controlar el desbordamiento. Dentro del div coloque otro div con un gradiente de "desvanecimiento" que se puede hacer utilizando CSS o una imagen (para IE antiguo). El degradado va de transparente al color de fondo de la celda de la tabla y es un poco más ancho que los puntos suspensivos. Si el texto es largo y se desborda, pasa por debajo del div "desvanecimiento" y se ve "desvanecido". Si el texto es corto, el desvanecimiento es invisible, por lo que no hay problema. Los dos contenedores se pueden ajustar para que se muestren una o varias líneas configurando la altura del contenedor como un múltiplo de la altura de la línea de texto. El div "fade" se puede colocar para cubrir solo la última línea.


Comparta las partes importantes de su solución, en SO no se permiten respuestas solo de enlace.
kapa

El aspecto brillante de esto es que el texto en sí no se trunca, por lo que si el usuario copia y pega la tabla, aparece todo el contenido.
prototipo el

Un muy buen concepto. También se menciona en este artículo (forma de "desvanecimiento") Creo css-tricks.com/line-clampin
Adrien Be

4

Aquí hay una manera pura de CSS para lograr esto: http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/

Aquí hay un resumen:

ingrese la descripción de la imagen aquí

<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

Puede usar la -webkit-line-clamppropiedad con div.

-webkit-line-clamp: <integer>lo que significa establecer el número máximo de líneas antes de truncar el contenido y luego muestra puntos suspensivos (…)al final de la última línea.

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
No estoy seguro de por qué alguien ha rechazado esta respuesta. El soporte del navegador a partir de marzo de 2020 es bastante decente - 95% caniuse.com/#search=line-clamp
Yulian

2

Aquí hay una solución JavaScript vainilla que puede usar en caso de apuro:

// @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>

Puedes jugar con él en el codepen a continuación. Intente cambiar el tamaño de fuente en el panel CSS y realice una edición menor en el panel HTML (por ejemplo, agregue un espacio adicional en algún lugar) para actualizar los resultados. Independientemente del tamaño de fuente, el párrafo central siempre debe truncarse al número de líneas en el segundo parámetro pasado a limitLines ().

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


2

EDITAR: me encontré con Shave, que es un complemento JS que hace un truncamiento de texto de varias líneas basado en una altura máxima dada realmente bien. Utiliza la búsqueda binaria para encontrar el punto de ruptura óptimo. Definitivamente vale la pena investigar.


RESPUESTA ORIGINAL:

Tuve que encontrar una solución JS de vainilla para este problema. En el caso en que había trabajado, tuve que ajustar un nombre de producto largo en un ancho limitado y en dos líneas; truncada por puntos suspensivos si es necesario.

Utilicé respuestas de varias publicaciones SO para preparar algo que se ajustara a mis necesidades. La estrategia es la siguiente:

  1. Calcule el ancho de caracteres promedio de la variante de fuente para el tamaño de fuente deseado.
  2. Calcular el ancho del contenedor
  3. Calcular el número de caracteres que caben en una línea en el contenedor
  4. Calcule la cantidad de caracteres para truncar la cadena en función de la cantidad de caracteres que caben en una línea y la cantidad de líneas que se supone que el texto debe ajustar.
  5. Trunca el texto de entrada basado en el cálculo anterior (factorizando los caracteres adicionales agregados por puntos suspensivos) y agrega "..." al final

Código de muestra:

/**
 * 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>

PD:

  1. Si el truncamiento debe estar en una sola línea, el método CSS puro de usar text-overflow: ellipsis es más ordenado
  2. Las fuentes que no tienen un ancho fijo pueden hacer que el truncamiento ocurra demasiado temprano o demasiado tarde (ya que los diferentes caracteres tienen diferentes anchos). El uso del parámetro pad ayuda a mitigar esto en algunos casos, pero no será infalible :)
  3. Agregaré enlaces y referencias a las publicaciones originales después de que recupere la computadora portátil (necesito historial)

PPS: Acabo de darme cuenta de que esto es muy similar al enfoque sugerido por @DanMan y @ st.never. Verifique los fragmentos de código para un ejemplo de implementación.


2

Solución javascript muy simple. Divs tiene que ser diseñado fe:

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

Y 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

No es una respuesta exacta a la pregunta, pero me encontré con esta página cuando intentaba hacer algo muy similar, pero quería agregar un enlace para "ver más" en lugar de simplemente puntos suspensivos. Esta es una función jQuery que agregará un enlace "más" al texto que desborda un contenedor. Personalmente, estoy usando esto con Bootstrap, pero por supuesto funcionará sin él.

Ejemplo más captura de pantalla

Para usar, coloque su texto en un contenedor de la siguiente manera:

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

Cuando se agrega la siguiente función jQuery, cualquiera de los divs que sean más grandes que el valor de ajuste de altura se truncará y se agregará un enlace "Más".

$(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);
        }
    });
});

Basado en esto, pero actualizado: http://shakenandstirredweb.com/240/jquery-moreless-text


<suspiro> Pensé que alguien podría rechazar esto, presumiblemente porque no es una respuesta exacta a la pregunta. Sin embargo, espero que alguien lo encuentre útil, ya que no pude obtener esta información en ningún otro lugar y aquí es donde terminé después de una búsqueda.
Andy Beverley

1

El plugin dotdotdot jQuery mencionado funciona bien con 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);

El marcado correspondiente sería:

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

1

Demo JS pura (sin jQuery y bucle 'while')

Cuando busqué la solución del problema de elipsis multilínea, me sorprendió que no haya ninguna buena sin jQuery. También hay algunas soluciones basadas en el ciclo 'while', pero creo que no son efectivas y peligrosas debido a la posibilidad de entrar en el ciclo infinito. Entonces escribí este código:

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);

1

Tal vez bastante tarde, pero usando SCSS puede declarar una función como:

@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%
      );
    }
  }
}

Y úsalo como:

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

Lo cual truncará el texto en una línea y sabiendo que es 1.4 su altura de línea. La salida esperada es de cromo para renderizar ...al final y FF con un desvanecimiento genial al final

Firefox

ingrese la descripción de la imagen aquí

Cromo

ingrese la descripción de la imagen aquí



0

Probablemente no pueda hacerlo (¿actualmente?) Sin una fuente de ancho fijo como Courier. Con una fuente de ancho fijo, cada letra ocupa el mismo espacio horizontal, por lo que probablemente pueda contar las letras y multiplicar el resultado con el tamaño de fuente actual en ems o exs. Luego, solo tendría que probar cuántas letras caben en una línea y luego dividirlas.

Alternativamente, para fuentes no fijas, es posible que pueda crear una asignación para todos los caracteres posibles (como i = 2px, m = 5px) y luego hacer los cálculos. Aunque mucho trabajo feo.


0

Para ampliar la solución de @ DanMan: en el caso de que se usen fuentes de ancho variable, puede usar un ancho de fuente promedio. Esto tiene dos problemas: 1) un texto con demasiados W se desbordaría y 2) un texto con demasiados I se truncaría antes.

O podría adoptar el enfoque del peor de los casos y utilizar el ancho de la letra "W" (que creo que es la más amplia). Esto elimina el problema 1 anterior pero intensifica el problema 2.

Un enfoque diferente podría ser: dejar overflow: clipen el div y agregar una sección de puntos suspensivos (tal vez otro div o imagen) con float: right; position: relative; bottom: 0px;(sin probar). El truco es hacer que la imagen aparezca sobre el final del texto.

También solo puede mostrar la imagen cuando sabe que se desbordará (por ejemplo, después de unos 100 caracteres)


¿Qué es overflow: clip? ¿Y qué esperarías que floathaga CSS ?
kapa

0

Con este código no hay necesidad de un contenedor adicional div si el elemento tiene su altura limitada por un estilo de altura máxima.

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

También guarda el texto original en un atributo de datos que se puede mostrar utilizando solo estilos, por ejemplo el ratón por encima:

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

1
Eso es a menudo un bucle infinito.
Atadj

0

En mi situación, no pude trabajar con ninguna de las funciones mencionadas anteriormente y también tuve que decirle a la función cuántas líneas mostrar, independientemente del tamaño de fuente o el tamaño del contenedor.

Basé mi solución en el uso del método Canvas.measureText (que es una función HTML5 ) como lo explica Domi aquí, por lo que no es completamente un navegador cruzado.

Puedes ver cómo funciona en este violín .

Este es el código:

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));
    });
})();

Y el HTML para usarlo sería así:

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

El código para obtener la familia de fuentes es bastante simple, y en mi caso funciona, pero para escenarios más complejos puede que necesite usar algo en esta línea .

Además, en mi caso le digo a la función cuántas líneas usar, pero podría calcular cuántas líneas mostrar de acuerdo con el tamaño del contenedor y la fuente.


0

He creado una versión que deja el html intacto. Ejemplo de 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;
}

Ejemplo de jsfiddle


0

Escribí un componente angular que resuelve el problema. Divide un texto dado en elementos span. Después de renderizar, elimina todos los elementos que se desbordan y coloca los puntos suspensivos justo después del último elemento visible.

Ejemplo de uso:

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

Demo de Stackblitz: https://stackblitz.com/edit/angular-wfdqtd

El componente:

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


-2

No estoy seguro de si esto es lo que está buscando, usa altura mínima en lugar de altura.

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

44
El problema es el número 63 en sus códigos, si se conoce el número, entonces todo se vuelve fácil, solo una función truncada hace este trabajo, como sus códigos. Sin embargo, ¿cómo saber el número? En otras palabras, ¿cómo saber dónde se romperá la línea del texto? Si esta pregunta puede responderse, entonces el problema puede resolverse simplemente en la lógica de "1, calcular el número; 2, truncar"
Edward

-3

Func muy simple hará.

Directiva:

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

Ver:

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

3
Como ya discutimos en otras respuestas, el problema en su código es el número 36. Además de hacer que su código sea específico para un determinado ancho de contenedor, tampoco es exacto: sin fuentes de ancho fijo, puede haber grandes diferencias entre letras . Vea iiiiiiiiiivs MMMMMMMMMM(aunque la fuente actual no es tan visible: D).
kapa
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.