web-dev-qa-db-fra.com

Débordement de texte multiligne sur plusieurs navigateurs avec Ellipsis ajoutés dans une largeur et une hauteur fixes `<div>`

J'ai créé une image pour cette question afin de la rendre plus facile à comprendre.

Est-il possible de créer un Ellipsis sur un <div> avec une largeur fixe et plusieurs lignes?

text-overflow

J’ai essayé quelques plugins jQuery ici et là, mais je ne trouve pas celui que je cherche. Toute recommandation? Des idées?

167
Edward

Juste une idée de base rapide.

Je testais avec le balisage suivant:

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

Et CSS:

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

L'application de cette requête jQuery donnera le résultat souhaité:

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

Il tente à plusieurs reprises de supprimer le dernier mot du texte jusqu'à ce qu'il atteigne la taille souhaitée. À cause du débordement: caché; le processus reste invisible et même si JS est désactivé, le résultat reste "correct du point de vue visuel" (sans le "..." bien sûr).

Si vous combinez cela avec une troncature sensible côté serveur (qui ne laisse qu'un léger surcoût), cela s'exécutera plus rapidement :).

Encore une fois, ce n’est pas une solution complète, mais une idée.

UPDATE: Ajout d'un démonstration de jsFiddle .

86
kapa

Essayez le plugin jQuery.dotdotdot .

$(".Ellipsis").dotdotdot();
67
Matt

Bibliothèques javascript pour "line clamping"

Notez que le "clampage de ligne" est également appelé "Ellipsis sur bloc de multi-lignes" ou "Ellipsis vertical".


github.com/BeSite/jQuery.dotdotdot


github.com/josephschmitt/Clamp.js


Voici quelques autres que je n'ai pas encore enquêté:


Solutions CSS pour le clampage de ligne

Il existe quelques solutions CSS, mais les utilisations les plus simples -webkit-line-clamp qui supporte mal le navigateur . Voir la démo en direct sur jsfiddle.net/AdrienBe/jthu55v7/

Beaucoup de gens ont fait de gros efforts pour y arriver en utilisant uniquement CSS. Voir les articles et les questions à ce sujet:


Ce que je recommanderais

Rester simple. Sauf si vous avez beaucoup de temps à consacrer à cette fonctionnalité, optez pour la solution la plus simple et testée: un simple CSS ou une bibliothèque javascript bien testée.

Optez pour quelque chose d'extraordinaire/complexe/hautement personnalisé et vous en paierez le prix par la suite.


Ce que font les autres

Avoir un fondu comme Airbnb pourrait être une bonne solution. Il s’agit probablement de CSS de base associé à jQuery de base. En fait, cela ressemble beaucoup à cette solution sur CSSTricks

AirBnb "read more" solution

Oh, et si vous recherchez des inspirations de design:

22
Adrien Be

Il n’existe pas de telle fonctionnalité en HTML, ce qui est très frustrant.

J'ai développé un bibliothèque pour gérer cela.

  • Débordement de texte multiligne: Ellipsis
  • Texte multiligne avec des technologies qui ne le supportent pas: SVG, Canvas par exemple
  • Ayez exactement les mêmes sauts de ligne dans votre texte SVG, dans votre rendu HTML et dans votre exportation PDF par exemple).

Consultez mon site pour la capture d'écran, le tutoriel et le lien de téléchargement.

6
Samuel Rossille

Solution Pure JS basée sur la solution de bažmegakapa, et nettoyage permettant de prendre en compte les personnes qui tentent de donner une hauteur/hauteur maximale inférieure à la hauteur de la ligne de l'élément:

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

J'ai une solution qui fonctionne bien mais à la place d'un Ellipsis, il utilise un dégradé. Les avantages sont que vous n'avez pas à faire de calculs JavaScript et que cela fonctionne pour les conteneurs de largeur variable, y compris les cellules de tableau. Il utilise quelques div supplémentaires, mais il est très facile à mettre en œuvre.

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

Edit: Désolé, je ne savais pas que le lien ne suffisait pas. La solution consiste à placer une div autour du texte et à la styler pour contrôler le débordement. À l'intérieur de la div, mettez une autre div avec un dégradé de "fondu" qui peut être créé en utilisant CSS ou une image (pour les anciens IE). Le dégradé va du transparent à la couleur d'arrière-plan de la cellule du tableau et est un peu plus large qu'un Ellipsis. Si le texte est long et qu'il déborde, il passe sous la "fade" div et a l’air "effacé". Si le texte est court, le fondu est invisible, il n'y a donc aucun problème. Les deux conteneurs peuvent être ajustés pour laisser apparaître une ou plusieurs lignes en définissant la hauteur du conteneur comme un multiple de la hauteur de la ligne de texte. Le div "fade" peut être positionné pour ne couvrir que la dernière ligne.

4
Miriam Salzer

Voici un moyen purement CSS d’y parvenir: http://www.mobify.com/blog/multiline-Ellipsis-in-pure-css/

Voici un résumé:

enter image description here

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

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

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

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

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

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

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

        text-align: right;

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

Voici une solution JavaScript Vanille que vous pouvez utiliser rapidement:

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

Vous pouvez jouer avec cela dans le code ci-dessous. Essayez de modifier la taille de la police dans le panneau CSS et effectuez une modification mineure dans le panneau HTML (par exemple, ajoutez un espace supplémentaire quelque part) pour mettre à jour les résultats. Quelle que soit la taille de la police, le paragraphe du milieu doit toujours être tronqué au nombre de lignes du deuxième paramètre passé à limitLines ().

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

2
thdoan

Le plugin mentionné dotdotdot jQuery fonctionne bien avec 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);

Le balisage correspondant serait:

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

Solution javascript très simple. Divs doit être appelé f.e .:

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

Et 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
Michal Politzer

Pas une réponse exacte à la question, mais je suis tombé sur cette page en essayant de faire très similaire, mais en voulant ajouter un lien pour "voir plus" plutôt que juste un Ellipsis simple. C'est une fonction jQuery qui ajoutera un lien "plus" au texte qui déborde d'un conteneur. Personnellement, je l’utilise avec Bootstrap, mais bien sûr, cela fonctionnera sans.

Example more screenshot

Pour l'utiliser, placez votre texte dans un conteneur comme suit:

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

Lorsque la fonction jQuery suivante est ajoutée, toute division supérieure à la valeur adjustheight sera tronquée et un lien "Plus" sera ajouté.

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

Basé sur cela, mais mis à jour: http://shakenandstirredweb.com/240/jquery-moreless-text

1
Andy Beverley

démo JS pure (sans jQuery et boucle 'while')

Quand j'ai cherché la solution du problème multiligne Ellipsis j'ai été surpris qu'il n'y a pas de bon sans jQuery. Il existe également quelques solutions basées sur la boucle 'while', mais je pense qu'elles ne sont ni efficaces ni dangereuses en raison de la possibilité d'entrer dans une boucle infinie. Alors j'ai écrit ce code:

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
WebBrother

Peut-être assez tard, mais en utilisant SCSS, vous pouvez déclarer une fonction comme:

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

Et utilisez-le comme:

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

Ce qui tronquera le texte en une ligne et sachant que c'est 1,4 sa hauteur de ligne. La sortie attendue est chrome à rendre avec ... à la fin et FF avec un fondu frais à la fin

Firefox

enter image description here

Chrome

enter image description here

1
Alexis Duran

EDIT: Came across Shave qui est un plug-in JS qui effectue une troncature de texte multiligne basée sur une hauteur maximale donnée. Il utilise la recherche binaire pour trouver le point de rupture optimal. Ça vaut vraiment la peine d’enquêter.


RÉPONSE ORIGINALE:

Je devais trouver une solution Vanilla JS pour résoudre ce problème. Dans le cas sur lequel j'avais travaillé, je devais adapter un nom de produit long en largeur limitée et sur deux lignes; tronqué par Ellipsis si nécessaire.

J'ai utilisé les réponses de divers SO messages pour concocter quelque chose qui correspond à mes besoins. La stratégie est la suivante:

  1. Calculez la largeur de caractère moyenne de la variante de police pour la taille de police souhaitée.
  2. Calculer la largeur du conteneur
  3. Calculer le nombre de caractères qui tiennent sur une ligne du conteneur
  4. Calculez le nombre de caractères pour tronquer la chaîne en fonction du nombre de caractères qui tiennent sur une ligne et du nombre de lignes que le texte est censé recouvrir.
  5. Tronque le texte d'entrée en fonction du calcul précédent (en tenant compte des caractères supplémentaires ajoutés par Ellipsis) et ajoute "..." à la fin

Exemple de code:

/**
 * 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. Si la troncature ne doit figurer que sur une seule ligne, la méthode CSS pure consistant à utiliser text-overflow: Ellipsis est plus claire.
  2. Les polices qui n'ont pas de largeur fixe peuvent provoquer la troncature trop tôt ou trop tard (les caractères différents ayant des largeurs différentes). L'utilisation du paramètre pad permet d'atténuer cela dans certains cas, mais ne sera pas infaillible :)
  3. Ajoutera des liens et des références aux publications originales après avoir récupéré mon ordinateur portable (historique nécessaire)

PPS: Je viens de me rendre compte que cette approche est très similaire à celle suggérée par @DanMan et @ st.never. Consultez les extraits de code pour un exemple d'implémentation.

1
Chirag Ravindra

Vous ne pouvez probablement pas le faire (actuellement?) Sans une police à largeur fixe comme Courier. Avec une police à largeur fixe, chaque lettre occupe le même espace horizontal. Vous pouvez donc probablement compter les lettres et multiplier le résultat par la taille de la police actuelle, en format em ou ex. Ensuite, il vous suffira de vérifier combien de lettres peuvent contenir une ligne, puis de les séparer.

Sinon, vous pouvez éventuellement créer un mappage pour tous les caractères possibles (comme i = 2px, m = 5px), puis faire le calcul. Beaucoup de travail moche cependant.

0
DanMan

Pour développer la solution de @ DanMan: dans le cas où des polices à largeur variable sont utilisées, vous pouvez utiliser une largeur de police moyenne. Cela pose deux problèmes: 1) un texte avec trop de W déborderait et 2) un texte avec trop de I serait tronqué plus tôt.

Ou vous pourriez adopter une approche pire et utiliser la largeur de la lettre "W" (qui, à mon avis, est la plus large). Cela supprime le problème 1 ci-dessus mais intensifie le problème 2.

Une approche différente pourrait être: laisser overflow: clip dans le div et ajoutez une section Ellipsis (peut-être un autre div ou une image) avec float: right; position: relative; bottom: 0px; (non testé). L'astuce consiste à faire apparaître l'image au-dessus de la fin du texte.

Vous pouvez également uniquement afficher l'image lorsque vous savez qu'elle débordera (après environ 100 caractères).

0
st.never

J'ai fait une version qui laisse le code HTML intact. exemple 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;
}

exemple jsfiddle

0
Wow

J'ai écrit un composant angular qui résout le problème. Il divise un texte donné en éléments étendus. Après le rendu, il supprime tous les éléments débordants et place l'Ellipsis juste après le dernier élément visible.

Exemple d'utilisation:

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

Démo Stackblitz: https://stackblitz.com/edit/angular-wfdqtd

Le composant:

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

Dans mon scénario, je ne pouvais utiliser aucune des fonctions mentionnées ci-dessus et je devais également indiquer à la fonction le nombre de lignes à afficher, quelle que soit la taille de la police ou du conteneur.

J'ai basé ma solution sur l'utilisation de la méthode Canvas.measureText (qui est un HTML5 caractéristique) comme expliqué ici par Domi , donc n'est pas complètement cross-browser.

Vous pouvez voir comment cela fonctionne sur ce violon .

C'est le code:

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

Et le HTML à utiliser serait comme ça:

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

Le code pour obtenir la famille de polices est assez simple et, dans mon cas, fonctionne, mais pour des scénarios plus complexes, vous devrez peut-être utiliser quelque chose le long de ces lignes .

En outre, dans mon cas, je dis à la fonction le nombre de lignes à utiliser, mais vous pouvez calculer le nombre de lignes à afficher en fonction de la taille du conteneur et de la police.

0
Dzyann

Ici, j'ai créé une autre bibliothèque avec un algorithme plus rapide. Vérifiez s'il vous plaît:

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

Pour installer à l’aide de bower:

bower install fast-Ellipsis

Pour installer à l’aide de npm:

npm install fast-Ellipsis 

Espérons que vous apprécierez!

0
I. Ahmed

Avec ce code, il n'est pas nécessaire d'ajouter une classe wrapper supplémentaire si la hauteur de l'élément est limitée par un style hauteur maximale.

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

En outre, il enregistre le texte d'origine dans un attribut de données qui peut être affiché en utilisant uniquement des styles, par exemple. au survol de la souris:

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