Débordement de texte multiligne inter-navigateurs avec des points de suspension ajoutés dans une largeur et une hauteur fixes


178

J'ai fait une image pour cette question pour la rendre plus facile à comprendre.

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

dépassement de texte

J'ai essayé des plugins jQuery ici et là, mais je ne trouve pas celui que je recherche. Une recommandation? Des idées?



1
et stackoverflow.com/questions/3922739/… pour une solution css uniquement
Evgeny


2
Pour tous ceux qui recherchent cela à la mi-2016, la réponse courte est: NON, ce n'est pas possible dans un navigateur élégant, croisé, CSS uniquement. La solution souvent donnée comme la plus proche de la fin ( codepen.io/romanrudenko/pen/ymHFh ) est tellement Goldbergienne qu'elle fait mal à tout votre corps, et reste assez moche.
konrad

Réponses:


91

Juste une petite idée de base.

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 ce jQuery obtiendra 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 essaie à plusieurs reprises de supprimer le dernier mot du texte jusqu'à ce qu'il atteigne la taille souhaitée. En raison du débordement: caché; le processus reste invisible et même avec JS désactivé, le résultat reste «visuellement correct» (sans le «...» bien sûr).

Si vous combinez cela avec une troncature judicieuse côté serveur (qui ne laisse qu'une petite surcharge), cela fonctionnera plus vite :).

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

MISE À JOUR: Ajout d'une démo jsFiddle .


1
excellente solution @bazmegakapa ... mais j'ai quelques problèmes en essayant de l'adapter à mon cas. J'en ai plusieurs liet à l'intérieur de chacun il y a un .blocket un .block h2et je dois l'appliquer à l' h2intérieur .blockmais je n'ai pas pu le faire fonctionner. Est-ce différent s'il y en a plus d'un .block h2?
Alex

1
Dans mon cas, il ne laissait que 2 lignes de texte alors qu'il aurait dû y en avoir 3. Apparemment, mon conteneur était plus petit que la ligne height*3de quelques pixels. La solution facile consiste simplement à ajouter quelques pixels àdivh
Lukas LT

3
J'ai eu une mauvaise expérience de boucle infinie avec ce script car le texte ne contenait qu'un seul mot très long, donc l'expression rationnelle replace ne correspondait jamais. Pour éviter cela, ajoutez ce code juste après la whileligne:if(!$p.text().match(/\W*\s(\S)*$/)) break;
KrisWebDev

1
Bien que cela ne soit probablement pas un problème dans ce cas, la mise à jour du DOM et la vérification répétée de la mise en page sont une mauvaise idée car cela pourrait ralentir. Pour atténuer cela, vous pouvez quelque chose de similaire à une recherche binaire: testez pour voir si le bloc de texte correspond déjà, sinon divisez le texte en mots ou en caractères et définissez vos limites (inférieur = 1 mot / caractères, supérieur = tous les mots / caractères) , 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;}}. Comme @KrisWebDev l'a trouvé, vous voudrez également rechercher un mot géant.
Chinoto Vokro

1
Cette solution est excellente. Dans mon cas, je dois garder une trace du texte original afin de pouvoir tronquer la valeur du texte intégral de manière réactive. Ainsi, lorsque la page se charge, je stocke le texte d'origine dans une variable, et avant d'exécuter cette logique, je m'assure de «rafraîchir» l'élément avec la valeur de texte d'origine. Ajoutez un anti-rebond et cela fonctionne à merveille.
besseddrest

68

Essayez le plugin jQuery.dotdotdot .

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

11
Comment prononceriez-vous cela? point-point-point-point?
JackAce

58
Vraiment nul d'utiliser plus de 600 lignes de js pour résoudre un problème qui devrait être résolu par css
Jethro Larson

Je l'ai essayé et cela fonctionne bien. Devrait être la réponse acceptée
AbdelHady

1
Fonctionne mais assurez-vous d'utiliser l'événement window.loaded et non $ (document) .ready (), car les polices et autres ressources externes peuvent avoir un impact sur la mise en page de votre HTML. Si dotdotdot s'exécute avant le chargement de ces ressources, le texte ne sera pas tronqué à la bonne position.
sboisse

10
Il s'agit d'un outil commercial, il coûte 5 $ pour un seul site et 35 $ pour plusieurs sites. Une licence serait une douleur. Je pensais que c'était gratuit et immédiatement intégrable, non!
gène b.

29

Bibliothèques Javascript pour "line clamping"

Notez que le "serrage de ligne" est également appelé "Ellipse sur bloc de multi-lignes" ou "ellipse verticale".


github.com/BeSite/jQuery.dotdotdot


github.com/josephschmitt/Clamp.js


En voici quelques autres sur lesquelles je n'ai pas encore enquêté:


Solutions CSS pour le serrage de ligne

Il existe des solutions CSS, mais les utilisations les plus simples -webkit-line-clampont une mauvaise prise en charge du navigateur . Voir la démo en direct sur jsfiddle.net/AdrienBe/jthu55v7/

Beaucoup de gens ont déployé de grands efforts pour que cela se produise en utilisant uniquement CSS. Voir les articles et les questions à ce sujet:


Ce que je recommanderais

Rester simple. À moins que vous n'ayez beaucoup de temps à consacrer à cette fonctionnalité, optez pour la solution la plus simple et la plus testée: un CSS simple ou une bibliothèque javascript bien testée.

Optez pour quelque chose de sophistiqué / complexe / hautement personnalisé et vous en paierez le prix plus tard.


Ce que font les autres

Avoir un fondu comme Airbnb pourrait être une bonne solution. Il s'agit probablement d'un CSS de base couplé à un jQuery de base. En fait, cela semble très similaire à cette solution sur CSSTricks

Solution AirBnb "en savoir plus"

Oh, et si vous recherchez des inspirations de design:


6

Il n'y a pas de telle fonctionnalité en HTML, et c'est très frustrant.

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

  • Débordement de texte multiligne: points de suspension
  • Texte multiligne avec des technologies qui ne le supportent pas: SVG, Canvas par exemple
  • Avoir exactement les mêmes sauts de ligne dans votre texte SVG, dans votre rendu HTML, et dans votre export PDF par exemple

Consultez mon site pour une capture d'écran, un didacticiel et un lien de téléchargement.


"erreur lors de l'établissement de la connexion à la base de données" ... vous voudrez peut-être faire comme tout le monde et héberger votre projet sur Github, ce serait probablement plus agréable pour vous et pour la communauté :)
Adrien Be

@AdrienBe c'est sur Github: github.com/rossille/jstext , et tu as raison, github étant plus stable que mon site, j'ai mis la page github comme lien principal
Samuel Rossille

@SamuelRossille bonne nouvelle, merci pour la mise à jour rapide!
Adrien Be

4

Solution JS pure basée sur la solution de bažmegakapa, et quelques nettoyages pour tenir compte des personnes qui essaient de donner une hauteur / hauteur maximale inférieure à 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);

C'est un code très inefficace. Par ailleurs, l'utilisation de "while" look est un bug potentiel avec une boucle infinie.
WebBrother

4

J'ai une solution qui fonctionne bien mais à la place des points de suspension, elle utilise un dégradé. Les avantages sont que vous n'avez pas à effectuer 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 c'est très facile à implémenter.

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

Edit: Désolé, je ne savais pas que le lien n'était pas suffisant. La solution est de mettre un div autour du texte et de styliser le div pour contrôler le débordement. À l'intérieur du div mettez un autre div avec un dégradé de "fondu" qui peut être fait en utilisant CSS ou une image (pour l'ancien 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'une ellipse. Si le texte est long et déborde, il passe sous le div "fondu" et semble "fondu". Si le texte est court, le fondu est invisible donc il n'y a pas de 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.


Veuillez partager les parties importantes de votre solution, sur SO, les réponses par lien uniquement ne sont pas autorisées.
kapa

l'aspect brillant de ceci est que le texte lui-même n'est pas tronqué, donc si l'utilisateur copie-colle le tableau, tout le contenu apparaît.
prototype

Un très beau concept. Il est également mentionné dans cet article (façon "fade out") je crois css-tricks.com/line-clampin
Adrien Be

4

Voici un moyen CSS pur pour y parvenir: http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/

Voici un résumé:

entrez la description de l'image ici

<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

Vous pouvez utiliser la -webkit-line-clamppropriété avec div.

-webkit-line-clamp: <integer>ce qui signifie définir le nombre maximum de lignes avant de tronquer le contenu, puis afficher une ellipse (…)à la fin de la dernière ligne.

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
Je ne sais pas pourquoi quelqu'un a voté contre cette réponse. Le support du navigateur à partir de mars 2020 est assez correct - 95% caniuse.com/#search=line-clamp
Yulian

2

Voici une solution JavaScript vanille que vous pouvez utiliser à la rigueur:

// @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 dans le codepen ci-dessous. Essayez de changer la taille de la police dans le panneau CSS et faites 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 dans le deuxième paramètre passé à limitLines ().

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


2

EDIT: Je suis tombé sur tombé Shave, un plugin JS qui fait très bien la troncature de texte sur plusieurs lignes en fonction d'une hauteur maximale donnée. Il utilise la recherche binaire pour trouver le point de rupture optimal. Vaut vraiment le détour.


RÉPONSE ORIGINALE:

J'ai dû trouver une solution JS vanille pour ce problème. Dans le cas sur lequel j'avais travaillé, je devais adapter un long nom de produit à une largeur limitée et sur deux lignes; tronqué par des points de suspension si nécessaire.

J'ai utilisé les réponses de divers messages SO pour préparer 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 dans le 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. Tronquez le texte d'entrée en fonction du calcul précédent (en tenant compte des caractères supplémentaires ajoutés par des points de suspension) et ajoutez "..." à 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 doit être sur une seule ligne, la méthode CSS pure d'utilisation de text-overflow: ellipsis est plus nette
  2. Les polices qui n'ont pas une largeur fixe peuvent provoquer la troncature trop tôt ou trop tard (car différents caractères ont 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 articles originaux après avoir récupéré mon ordinateur portable (besoin d'historique)

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


2

Solution javascript très simple. Divs doit être stylé par exemple:

.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

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 une simple ellipse. Il s'agit d'une fonction jQuery qui ajoutera un lien "plus" vers du texte qui déborde d'un conteneur. Personnellement, je l'utilise avec Bootstrap, mais bien sûr, cela fonctionnera sans.

Exemple plus de capture d'écran

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, tous les div qui sont plus grands que la valeur Adjustheight seront tronqués 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 ceci, mais mis à jour: http://shakenandstirredweb.com/240/jquery-moreless-text


<sigh> J'ai pensé que quelqu'un pourrait voter contre cela, probablement parce que ce n'est pas une réponse exacte à la question. Néanmoins, j'espère que quelqu'un la trouvera utile, car je n'ai pu cette information nulle part ailleurs et c'est là que j'ai fini après une recherche.
Andy Beverley

1

Le plugin jQuery dotdotdot mentionné 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

Démo Pure JS (sans jQuery et boucle 'while')

Lorsque j'ai cherché une solution au problème des ellipses multilignes, j'ai été surpris qu'il n'y en ait pas de bon sans jQuery. Il existe également quelques solutions basées sur la boucle «while», mais je pense qu'elles ne sont pas efficaces et dangereuses en raison de la possibilité d'entrer dans une boucle infinie. J'ai donc é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

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 à une ligne et sachant qu'il est de 1,4 sa hauteur de ligne. La sortie attendue est le rendu de chrome avec ...à la fin et FF avec un fondu cool à la fin

Firefox

entrez la description de l'image ici

Chrome

entrez la description de l'image ici


1

J'ai trouvé cette courte solution CSS uniquement dans la réponse d'Adrien Be :

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

En mars 2020, la prise en charge du navigateur était de 95,3% , non prise en charge dans IE et Opera Mini. Fonctionne sur Chrome, Safari, Firefox et Edge.


0

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 avec la taille de police actuelle en ems ou exs. Ensuite, il vous suffirait de tester le nombre de lettres sur une ligne, puis de la diviser.

Sinon, pour les polices non fixes, vous pourrez peut-être créer un mappage pour tous les caractères possibles (comme i = 2px, m = 5px), puis faire le calcul. Beaucoup de travail laid cependant.


0

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 pouvez adopter une approche du pire des cas et utiliser la largeur de la lettre «W» (qui, je crois, 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: clipdans le div et ajouter une section de points de suspension (peut-être un autre div ou une image) avecfloat: 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 afficher l'image uniquement lorsque vous savez qu'elle va déborder (par exemple, après environ 100 caractères)


C'est quoi overflow: clip? Et qu'attendriez-vous de ce CSS float?
kapa

0

Avec ce code, il n'est pas nécessaire d'avoir un div wrapper supplémentaire si l'élément a sa hauteur limitée par un style max-height.

// Shorten texts in overflowed paragraphs to emulate Operas text-overflow: -o-ellipsis-lastline
$('.ellipsis-lastline').each(function(i, e) {
    var $e = $(e), original_content = $e.text();
    while (e.scrollHeight > e.clientHeight)
        $e.text($e.text().replace(/\W*\w+\W*$/, '…'));
    $e.attr('data-original-content', original_content);
});

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

1
C'est souvent une boucle infinie.
Atadj le

0

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 la taille du conteneur.

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

Vous pouvez voir comment cela fonctionne sur ce violon .

Voici 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 pour l'utiliser serait comme ceci:

<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 plutôt simple et, dans mon cas, fonctionne, mais pour des scénarios plus complexes, vous devrez peut-être utiliser quelque chose du genre .

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


0

J'ai fait une version qui laisse le 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

J'ai écrit un composant angulaire qui résout le problème. Il divise un texte donné en éléments span. Après le rendu, il supprime tous les éléments débordants et place les points de suspension 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

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!


-2

Je ne sais pas si c'est ce que vous recherchez, il utilise la hauteur minimale au lieu de la hauteur.

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

4
Le problème est le nombre 63 dans vos codes, si le nombre est connu alors tout devient facile, juste une fonction tronquée fait ce travail, comme vos codes. Cependant, comment connaître le nombre? En d'autres termes, comment savoir où le texte sera interrompu? Si cette question peut trouver une réponse, alors le problème peut être simplement résolu dans la logique de "1, calculer le nombre; 2, tronquer"
Edward

-3

Une fonction très simple fera l'affaire.

Directif:

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

Vue:

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

3
Comme nous l'avons déjà discuté sous d'autres réponses, le problème dans votre code est le numéro 36. Outre le fait de rendre votre code spécifique à une certaine largeur de conteneur, il n'est pas non plus précis: avec des polices non à largeur fixe, il peut y avoir de grandes différences entre les lettres . Voir iiiiiiiiiivs MMMMMMMMMM(avec la police actuelle pas si visible que: D).
kapa
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.