Estouro de texto de várias linhas entre navegadores com reticências anexadas a uma largura e altura fixas

178

Fiz uma imagem para esta pergunta para facilitar a compreensão.

É possível criar reticências em uma <div>largura fixa e várias linhas?

excesso de texto

Eu tentei alguns plugins jQuery aqui e ali, mas não consigo encontrar o que estou procurando. Alguma recomendação? Ideias?

Edward
fonte
1
e stackoverflow.com/questions/3922739/… para solução somente CSS
Evgeny
artigo relacionado css-tricks.com/line-clampin
Adrien Seja
2
Para quem procura isso em meados de 2016, a resposta curta é: NÃO, não é possível de maneira elegante e cruzada, apenas CSS. A solução geralmente dada como a mais próxima da conclusão ( codepen.io/romanrudenko/pen/ymHFh ) é tão goldbergiana que faz todo o seu corpo doer e ainda é muito feio.
precisa

Respostas:

91

Apenas uma ideia básica rápida.

Eu estava testando com a seguinte marcação:

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

E CSS:

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

A aplicação desse jQuery alcançará o resultado desejado:

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

Ele tenta remover repetidamente a última palavra do texto até atingir o tamanho desejado. Por causa do estouro: oculto; o processo permanece invisível e, mesmo com o JS desativado, o resultado permanece 'visualmente correto' (sem o "...", é claro).

Se você combinar isso com um truncamento sensível no lado do servidor (isso deixa apenas uma pequena sobrecarga), ele será executado mais rapidamente :).

Novamente, essa não é uma solução completa, apenas uma idéia.

UPDATE: adicionada uma demonstração do jsFiddle .

kapa
fonte
1
ótima solução @bazmegakapa ... mas tenho alguns problemas tentando adaptá-lo ao meu caso. Eu tenho vários lie dentro de cada um existe um .blocke um .block h2e preciso aplicar isso ao h2interior, .blockmas não consegui fazê-lo funcionar. É diferente se houver mais de um .block h2?
Alex
1
No meu caso, ele estava deixando apenas duas linhas de texto quando deveria haver 3. Aparentemente, meu contêiner era menor que a linha height*3por alguns pixels. Reparo fácil é simplesmente adicionar alguns pixels paradivh
Lukas LT
3
Eu tive uma experiência ruim de loop infinito com esse script porque o texto continha apenas uma palavra muito longa, portanto o regexp de substituição nunca correspondia. Para evitar isso, adicione este código logo após a whilelinha:if(!$p.text().match(/\W*\s(\S)*$/)) break;
KrisWebDev
1
Embora provavelmente não seja um problema nesse caso, atualizar o DOM e verificar o layout repetidamente é uma má idéia, pois pode causar uma lentidão. Para atenuar isso, você pode fazer algo semelhante a uma pesquisa binária: testar para ver se o bloco de texto já se encaixa; caso contrário, divida o texto em palavras ou caracteres e defina seus limites (inferior = 1 palavra / caracteres, superior = todas as palavras / 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 o @KrisWebDev encontrou, você também deve procurar uma palavra gigante.
Chinoto Vokro 20/01/19
1
Esta solução é ótima. No meu caso, preciso acompanhar o texto original para poder truncar o valor do texto completo responsivamente. Portanto, quando a página é carregada, eu armazeno o texto original em uma variável e, antes de executar essa lógica, certifique-se de 'atualizar' o elemento com o valor do texto original. Adicione debounce e funciona maravilhosamente.
besseddrest
68

Experimente o plugin jQuery.dotdotdot .

$(".ellipsis").dotdotdot();
Matt
fonte
11
Como você pronuncia isso? ponto-ponto-ponto-ponto?
JackAce
58
Realmente é uma porcaria de usar> 600 linhas de js para resolver um problema que deve ser resolvido por css
Jethro Larson
Eu tentei e está funcionando bem. Deve ser a resposta aceita
AbdelHady
1
Funciona, mas certifique-se de usar o evento window.loaded e não $ (document) .ready (), pois fontes e outros recursos externos podem afetar o layout do seu HTML. Se o dotdotdot for executado antes do carregamento desses recursos, o texto não será truncado na posição correta.
sboisse
10
Essa é uma ferramenta comercial, custa US $ 5 para um único site e US $ 35 para vários sites. Licenciar seria uma dor. Eu pensei que era gratuito e imediatamente integrável, não tão!
gene b.
29

Bibliotecas Javascript para "fixação de linhas"

Observe que "fixação de linha" também é conhecida como "reticências no bloco de várias linhas" ou "reticências verticais".


github.com/BeSite/jQuery.dotdotdot


github.com/josephschmitt/Clamp.js


Aqui estão mais algumas que eu não investiguei ainda:


Soluções CSS para fixação de linhas

Existem algumas soluções de CSS, mas os usos mais simples, -webkit-line-clampcom pouco suporte ao navegador . Veja demonstração ao vivo em jsfiddle.net/AdrienBe/jthu55v7/

Muitas pessoas fizeram grandes esforços para que isso acontecesse usando apenas CSS. Veja artigos e perguntas sobre o assunto:


O que eu recomendaria

Mantenha simples. A menos que você tenha bastante tempo para se dedicar a esse recurso, opte pela solução mais simples e testada: CSS simples ou uma biblioteca javascript bem testada.

Escolha algo sofisticado / complexo / altamente personalizado e você pagará o preço por isso no futuro.


O que os outros fazem

Desaparecer como o Airbnb pode ser uma boa solução. Provavelmente é CSS básico, juntamente com jQuery básico. Na verdade, parece muito semelhante a esta solução no CSSTricks

AirBnb "leia mais" solução

Ah, e se você procurar inspirações de design:

Adrien Be
fonte
6

Não existe esse recurso no HTML, e isso é muito frustrante.

Eu desenvolvi uma biblioteca para lidar com isso.

  • Estouro de texto multilinha: reticências
  • Texto de várias linhas com tecnologias que não o suportam: SVG, Canvas, por exemplo
  • Tenha exatamente as mesmas quebras de linha no texto SVG, na renderização HTML e na exportação de PDF, por exemplo

Confira meu site para captura de tela, tutorial e link de transferência.

Samuel Rossille
fonte
"Erro ao estabelecer conexão db" ... você pode querer fazer como todo mundo e anfitrião seu projeto no Github, provavelmente seria mais agradável para você e para a comunidade :)
Adrien Seja
@AdrienBe é no Github: github.com/rossille/jstext , e você está certo, github ser mais estável do que o meu site, eu definir a página github como o principal elo
Samuel Rossille
@SamuelRossille ótimas notícias, obrigado pela atualização rápida!
Adrien Seja
4

Solução JS pura, baseada na solução de bažmegakapa, e alguma limpeza para dar conta de pessoas que tentam fornecer uma altura / altura máxima que seja menor que a linha do 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);
prashn64
fonte
Este é um código muito ineficaz. A propósito, o uso de "while" look é um bug em potencial com loop infinito.
WebBrother
4

Eu tenho uma solução que funciona bem, mas em vez de uma elipse, ela usa um gradiente. As vantagens são que você não precisa fazer nenhum cálculo de JavaScript e funciona para contêineres de largura variável, incluindo células da tabela. Ele usa algumas divs extras, mas é muito fácil de implementar.

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

Edit: Desculpe, eu não sabia que o link não era suficiente. A solução é colocar uma div ao redor do texto e estilizar a div para controlar o estouro. Dentro da div, coloque outra div com um gradiente "fade" que pode ser feito usando CSS ou uma imagem (para o IE antigo). O gradiente passa de transparente para a cor de fundo da célula da tabela e é um pouco mais largo que uma elipse. Se o texto for longo e estourar, ele ficará sob a div "fade" e parecerá "desbotado". Se o texto for curto, o desbotamento será invisível, portanto não haverá problema. Os dois contêineres podem ser ajustados para permitir que uma ou várias linhas sejam exibidas, definindo a altura do contêiner como um múltiplo da altura da linha de texto. A div "fade" pode ser posicionada para cobrir apenas a última linha.

Miriam Salzer
fonte
Compartilhe as partes importantes da sua solução, pois as respostas somente para links SO não são permitidas.
Kapa
o aspecto brilhante disso é que o texto em si não é truncado; portanto, se o usuário copiar e colar a tabela, todo o conteúdo será exibido.
Protótipo
Um conceito muito legal. Também é mencionado neste artigo ( "fade out" way) Eu acredito css-tricks.com/line-clampin
Adrien Seja
4

Aqui está uma maneira pura de CSS para fazer isso: http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/

Aqui está um resumo:

insira a descrição da imagem aqui

<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>
Aaron Hoffman
fonte
4

Você pode usar a -webkit-line-clamppropriedade com div.

-webkit-line-clamp: <integer>o que significa definir o número máximo de linhas antes de truncar o conteúdo e exibir reticências (…)no final da última linha.

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>

Penny Liu
fonte
2
Não sei por que alguém votou negativamente nesta resposta. O suporte ao navegador em março de 2020 é bastante decente - 95% caniuse.com/#search=line-clamp
Yulian
2

Aqui está uma solução JavaScript vanilla que você pode usar em uma pitada:

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

Você pode brincar com ele no código abaixo. Tente alterar o tamanho da fonte no painel CSS e faça uma pequena edição no painel HTML (por exemplo, adicione um espaço extra em algum lugar) para atualizar os resultados. Independentemente do tamanho da fonte, o parágrafo do meio sempre deve ser truncado para o número de linhas no segundo parâmetro passado para limitLines ().

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

thdoan
fonte
2

Edição: deparei com Shave, que é o plugin JS que faz o truncamento de texto de várias linhas com base em uma determinada altura máxima muito bem. Ele usa a pesquisa binária para encontrar o ponto de interrupção ideal. Definitivamente vale a pena investigar.


RESPOSTA ORIGINAL:

Eu tive que criar uma solução JS vanilla para esse problema. No caso em que trabalhei, tive que ajustar um nome de produto longo em largura limitada e em duas linhas; truncado por reticências, se necessário.

Eu usei respostas de vários posts do SO para preparar algo que atendesse às minhas necessidades. A estratégia é a seguinte:

  1. Calcule a largura média dos caracteres da variante de fonte para o tamanho de fonte desejado.
  2. Calcular a largura do contêiner
  3. Calcular o número de caracteres que cabem em uma linha no contêiner
  4. Calcule o número de caracteres para truncar a string com base no número de caracteres que cabem em uma linha e no número de linhas que o texto deve quebrar.
  5. Trunque o texto de entrada com base no cálculo anterior (considerando os caracteres extras adicionados por reticências) e acrescente "..." ao final

Exemplo de código:

/**
 * 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. Se o truncamento estiver em apenas uma linha, o método CSS puro de usar o excesso de texto: as reticências são mais limpas
  2. Fontes que não têm largura fixa podem causar o truncamento muito cedo ou muito tarde (como caracteres diferentes têm larguras diferentes). O uso do parâmetro pad ajuda a atenuar isso em alguns casos, mas não será à prova de idiotas :)
  3. Adicionarei links e referências às postagens originais depois que eu voltar ao laptop (histórico de necessidades)

PPS: Acabei de perceber que isso é muito semelhante à abordagem sugerida por @DanMan e @ st.never. Confira os trechos de código para obter um exemplo de implementação.

Chirag Ravindra
fonte
2

Solução javascript muito simples. Divs deve ser denominado fe:

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

E 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 + "…";
  }
}
Michal Politzer
fonte
1

Não é uma resposta exata para a pergunta, mas me deparei com esta página ao tentar fazer algo parecido, mas ao querer adicionar um link para "exibir mais", em vez de apenas reticências diretas. Esta é uma função jQuery que adicionará um link "mais" ao texto que está transbordando um contêiner. Pessoalmente, estou usando isso com o Bootstrap, mas é claro que funcionará sem.

Exemplo de mais captura de tela

Para usar, coloque seu texto em um contêiner da seguinte maneira:

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

Quando a seguinte função jQuery é adicionada, qualquer uma das divs maiores que o valor de ajuste da altura será truncada e terá um link "Mais" adicionado.

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

Com base nisso, mas atualizado: http://shakenandstirredweb.com/240/jquery-moreless-text

Andy Beverley
fonte
<Suspiro> Eu pensei que alguém poderia rebater isso, provavelmente porque não é uma resposta exata para a pergunta. No entanto, espero que alguém ache útil, pois não consegui essas informações em nenhum outro lugar e foi aqui que terminei após uma pesquisa.
Andy Beverley
1

O mencionado plugin do jQuery dotdotdot funciona bem com 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);

A marcação correspondente seria:

<p app-ellipsis>{{ selectedItem.Description }}</p>
Edward Olamisan
fonte
1

Demonstração JS pura (sem jQuery e loop 'while')

Quando pesquisei a solução do problema de reticências multilinhas, fiquei surpreso por não haver uma boa sem o jQuery. Também existem algumas soluções baseadas no loop 'while', mas acho que elas não são eficazes e perigosas devido à possibilidade de entrar no loop infinito. Então eu escrevi 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);
WebBrother
fonte
1

Talvez seja tarde demais, mas usando o SCSS você pode declarar uma função 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%
      );
    }
  }
}

E use-o como:

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

O que truncará o texto em uma linha e sabendo que ele tem 1,4 de sua altura. A saída esperada é que o chrome seja processado ...no final e o FF com algum desbotamento legal no final

Raposa de fogo

insira a descrição da imagem aqui

cromada

insira a descrição da imagem aqui

Alexis Duran
fonte
1

Encontrei esta solução curta apenas de CSS na resposta de Adrien Be :

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

Em março de 2020, o suporte ao navegador é de 95,3% , não sendo suportado no IE e no Opera Mini. Funciona no Chrome, Safari, Firefox e Edge.

Yulian
fonte
0

Você provavelmente não pode fazê-lo (atualmente?) Sem uma fonte de largura fixa como Courier. Com uma fonte de largura fixa, todas as letras ocupam o mesmo espaço horizontal; portanto, você provavelmente pode contar as letras e multiplicar o resultado pelo tamanho da fonte atual em ems ou exs. Depois, basta testar quantas letras cabem em uma linha e depois separá-las.

Como alternativa, para fontes não fixas, você pode criar um mapeamento para todos os caracteres possíveis (como i = 2px, m = 5px) e depois fazer as contas. Um monte de trabalho feio embora.

DanMan
fonte
0

Para expandir a solução da @ DanMan: no caso de fontes com largura variável, você pode usar uma largura média. Isso tem dois problemas: 1) um texto com muitos Ws seria estourado e 2) um texto com muitos Is seria truncado anteriormente.

Ou você pode adotar uma abordagem de pior caso e usar a largura da letra "W" (que acredito ser a mais ampla). Isso remove o problema 1 acima, mas intensifica o problema 2.

Uma abordagem diferente poderia ser: deixe overflow: clipna div e adicione uma seção de reticências (talvez outra div ou imagem) comfloat: right; position: relative; bottom: 0px; (não testado). O truque é fazer a imagem aparecer acima do final do texto.

Você também pode exibir a imagem apenas quando sabe que ela está transbordando (digamos, após cerca de 100 caracteres)

nunca
fonte
O que é overflow: clip? E o que você esperaria que esse CSS floatfizesse?
Kapa
0

Com esse código, não há necessidade de uma div de wrapper extra se o elemento tiver sua altura limitada por um 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);
});

Além disso, ele salva o texto original em um atributo de dados que pode ser exibido usando apenas estilos, por exemplo. ao passar o mouse:

.ellipsis-lastline {
    max-height: 5em;
}
.ellipsis-lastline:before {
    content: attr(data-original-content);
    position: absolute;
    display: none;
}
.ellipsis-lastline:hover:before {
    display: block;
}
Johan
fonte
1
Isso geralmente é um loop infinito.
Atadj
0

No meu cenário, não consegui trabalhar com nenhuma das funções mencionadas acima e também precisava informar à função quantas linhas exibir, independentemente do tamanho da fonte ou do tamanho do contêiner.

Baseei minha solução no uso do método Canvas.measureText (que é um recurso HTML5 ), conforme explicado aqui pela Domi , para que não seja completamente compatível com vários navegadores.

Você pode ver como ele funciona nesse violino .

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

E o HTML para usá-lo seria assim:

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

O código para obter a família de fontes é bastante simples e, no meu caso, funciona, mas para cenários mais complexos, você pode precisar usar algo nesse sentido .

Além disso, no meu caso, estou dizendo à função quantas linhas usar, mas você pode calcular quantas linhas serão exibidas de acordo com o tamanho e a fonte do contêiner.

Dzyann
fonte
0

Eu fiz uma versão que deixa o html intacto. exemplo 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;
}

exemplo jsfiddle

Uau
fonte
0

Eu escrevi um componente angular que resolve o problema. Ele divide um determinado texto em elementos de amplitude. Após a renderização, ele remove todos os elementos que transbordam e coloca as reticências logo após o último elemento visível.

Exemplo de uso:

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

Demonstração do Stackblitz: https://stackblitz.com/edit/angular-wfdqtd

O 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];
  }
}
Martin Cremer
fonte
0

Aqui fiz outra biblioteca com algoritmo mais rápido. Por favor, verifique:

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

Para instalar usando o caramanchão:

bower install fast-ellipsis

Para instalar usando o npm:

npm install fast-ellipsis 

Espero que goste!

I. Ahmed
fonte
-2

não tenho certeza se é isso que você procura, ele usa altura mínima em vez 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>
Khairil
fonte
4
O problema é o número 63 nos seus códigos; se o número for conhecido, tudo ficará fácil; apenas uma função truncada fará esse trabalho, como seus códigos. No entanto, como saber o número? Em outras palavras, como saber onde o texto será quebrado? Se esta pergunta pode ser respondida, então o problema pode ser resolvido simplesmente na lógica de "1, calcular o número, 2, truncada"
Edward
-3

Uma função muito simples serve.

Directiva:

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

Visão:

<#p>{{truncateAlbumName(album.name)}}<#/p>
yamenator
fonte
3
Como já discutimos em outras respostas, o problema no seu código é o número 36. Além de tornar o código específico para uma certa largura de contêiner, também não é preciso: com fontes de largura não fixa, pode haver grandes diferenças entre as letras . Veja iiiiiiiiiivs MMMMMMMMMM(com a fonte atual que não é tão visível: D).
Kapa