HTML - comment afficher l'infobulle UNIQUEMENT lorsque les points de suspension sont activés

205

J'ai une étendue avec des données dynamiques dans ma page, avec ellipsisstyle.

.my-class
{
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;  
  width: 71px;
}
<span id="myId" class="my-class"></span>
document.getElementById('myId').innerText = "...";

Je voudrais ajouter à cette info-bulle d'élément avec le même contenu, mais je veux qu'elle n'apparaisse que lorsque le contenu est long et que les points de suspension apparaissent à l'écran.

Y a-t-il un moyen de le faire?
Le navigateur lance-t-il un événement lorsqu'il ellipsisest activé?

* Navigateur: Internet Explorer

Homme araignée
la source
1
Gardez à l'esprit que text-overflow:ellipsis;cela ne fonctionne pas du tout dans Firefox - voir cette question pour plus: stackoverflow.com/questions/4927257/…
Spudley
2
Je devais juste faire quelque chose de similaire. Vérifier si element.offsetWidth < element.scrollWidthselon cette réponse semble fonctionner jusqu'à présent.
Martin Smith
détection des points de suspension: stackoverflow.com/questions/7738117/…
Adrien Be
7
Remarque pour la postérité: text-overflow:ellipsis;fonctionne maintenant dans Firefox; il a été ajouté dans Firefox 7 (sorti en septembre 2011).
sleske

Réponses:

161

Voici une façon de le faire en utilisant le paramètre des points de suspension intégrés et en ajoutant l' titleattribut à la demande (avec jQuery) en s'appuyant sur le commentaire de Martin Smith:

$('.mightOverflow').bind('mouseenter', function(){
    var $this = $(this);

    if(this.offsetWidth < this.scrollWidth && !$this.attr('title')){
        $this.attr('title', $this.text());
    }
});
Jason Kleban
la source
4
Merci, fonctionne comme un charme! Cependant, si vous voulez le rendre plus efficace, vous pouvez envisager de remplacer bind () par on () comme ceci: $ (document) .on ('mouseenter', '.mightOverflow', function () {...});
Ziad
17
Si vous souhaitez que l'infobulle soit automatiquement supprimée si le texte ne déborde plus, modifiez-le en: $ (document) .on ('mouseenter', '.mightOverflow', function () {var $ t = $ (this); var title = $ t.attr ('title'); if (! title) {if (this.offsetWidth <this.scrollWidth) $ t.attr ('title', $ t.text ())} else {if (this.offsetWidth> = this.scrollWidth && title == $ t.text ()) $ t.removeAttr ('title')}});
Chris Janssen
1
mouseenter est l'événement auquel nous nous lions ou que nous écoutons. Nous n'avons pas besoin d'ajouter l'info-bulle aux éléments jusqu'à ce que quelqu'un passe la souris dessus. Nous reportons donc l'ajout jusqu'à ce point d'entrée de la souris sur l'un des éléments DOM avec la classe "mightoverflow". Just-In-Time-Tooltips
Jason Kleban
2
"As of jQuery 1.7, the .on() method is the preferred method for attaching event handlers to a document"pour plus d'informations, voir api.jquery.com/bind et api.jquery.com/on
Adrien Be
2
Cela ne fonctionne pas sur IE10 - offsetWidth est identique à scrollWidth, les deux me donnant la largeur tronquée.
SsjCosty
59

Voici une solution CSS pure. Pas besoin de jQuery. Il n'affichera pas d'infobulle, mais étendra simplement le contenu sur toute sa longueur au survol de la souris.

Fonctionne très bien si vous avez du contenu qui est remplacé. Ensuite, vous n'avez pas besoin d'exécuter une fonction jQuery à chaque fois.

.might-overflow {
    text-overflow: ellipsis;
    overflow : hidden;
    white-space: nowrap;
}

.might-overflow:hover {
    text-overflow: clip;
    white-space: normal;
    word-break: break-all;
}
Snæbjørn
la source
18
Ce n'est pas aussi bon qu'une info-bulle, car cela peut casser la disposition.
Alexander
2
Le problème avec les info-bulles est qu'elles ne fonctionnent pas sur mobile, du moins pour l'instant. Cette réponse pourrait (lire: va probablement) casser la disposition, mais elle pourrait être adaptée pour, par exemple, avoir une couleur d'arrière-plan différente pour l'élément survolé. Cela ne fonctionnerait toujours pas sur mobile, mais c'est une autre option pour le bureau.
trysis
1
Merci pour cette excellente réponse qui répond parfaitement à mes besoins. En utilisant du JavaScript, nous pourrions imaginer faire en sorte que le bloc soit positionné de façon à ne pas casser la disposition.
Niavlys
2
C'est une bonne réponse, mieux que de gonfler javascript sur un problème css. Il est clair que les points de suspension DEVRAIENT avoir une option de titre automatisée, la façon dont elle est implémentée pour le moment manque de sens. Dommage que le CSS n'autorise pas seulement: survoler si les derniers caractères sont '...'
John
32

La réponse de uosɐſ est fondamentalement correcte, mais vous ne voulez probablement pas le faire dans l'événement mouseenter. Cela va le faire faire le calcul pour déterminer s'il est nécessaire, chaque fois que vous passez la souris sur l'élément. À moins que la taille de l'élément ne change, il n'y a aucune raison de le faire.

Il serait préférable d'appeler ce code immédiatement après l'ajout de l'élément au DOM:

var $ele = $('#mightOverflow');
var ele = $ele.eq(0);
if (ele.offsetWidth < ele.scrollWidth)
    $ele.attr('title', $ele.text());

Ou, si vous ne savez pas exactement quand il est ajouté, appelez ce code une fois le chargement de la page terminé.

si vous avez plus d'un seul élément avec lequel vous devez le faire, vous pouvez leur donner la même classe (comme "mightOverflow") et utiliser ce code pour les mettre à jour tous:

$('.mightOverflow').each(function() {
    var $ele = $(this);
    if (this.offsetWidth < this.scrollWidth)
        $ele.attr('title', $ele.text());
});
Elezar
la source
+1, le faire au chargement de la page semble plus simple mais offre également plus de possibilités. c'est à dire. vous voudrez peut-être styliser cet élément qui a une info-bulle avec un soulignement en pointillé pour "signaler" l'info-bulle. comme$(".mightOverflow").each(function() { if( $(this).offsetWidth < $(this).scrollWidth && !$(this).attr('title')){ $(this).attr('title', $(this).text()); $(this).css('border-bottom', '1px dotted #A8A8A8'); } });
Adrien Be
'chaque fois que vous passez la souris sur' est faux (maintenant au moins - je ne sais pas si la réponse a été mise à jour) Il vérifie si elle a été calculée précédemment '&&! $ this.attr (' title ')'
Rune Jeppesen
Non, cela l'empêche simplement d'ajouter à nouveau l'attribut title. Le calcul pour déterminer s'il est nécessaire est toujours effectué. this.offsetWidth < this.scrollWidthet éventuellement même la vérification !$this.attr('title')sera effectuée chaque fois que vous passez la souris sur l'élément.
Elezar
9
Le problème avec cette méthode est que tous les éléments sont vérifiés en même temps (impact potentiel sur les performances) et qu'elle ne calcule qu'une seule fois, de sorte que si l'utilisateur rétrécit le navigateur provoquant la troncature ou l'expansion des éléments, les empêchant d'être tronqués, vous ne pouvez plus mettre à jour la présence d'une info-bulle. Attacher à nouveau votre code au redimensionnement de la fenêtre aurait un problème de performances car chaque élément vérifie sa taille. En utilisant la délégation d'événement "$ (document) .on ('mouseenter', '.mightOverflow', ..." et en retardant la vérification jusqu'à ce que vous survoliez l'élément, vous pouvez mettre à jour à la volée et vérifier seulement 1 élément @ à la fois
Chris Janssen
Cela ne fonctionne pas sur IE10 - offsetWidth est identique à scrollWidth, les deux me donnant la largeur tronquée.
SsjCosty
18

Voici deux autres solutions CSS pures:

  1. Afficher le texte tronqué en place:

.overflow {
  overflow: hidden;
  -ms-text-overflow: ellipsis;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.overflow:hover {
  overflow: visible;
}

.overflow:hover span {
  position: relative;
  background-color: white;

  box-shadow: 0 0 4px 0 black;
  border-radius: 1px;
}
<div>
  <span class="overflow" style="float: left; width: 50px">
    <span>Long text that might overflow.</span>
  </span>
  Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad recusandae perspiciatis accusantium quas aut explicabo ab. Doloremque quam eos, alias dolore, iusto pariatur earum, ullam, quidem dolores deleniti perspiciatis omnis.
</div>

  1. Afficher une "info-bulle" arbitraire :

.wrap {
  position: relative;
}

.overflow {
  white-space: nowrap; 
  overflow: hidden;
  text-overflow: ellipsis;
  
  pointer-events:none;
}

.overflow:after {
  content:"";
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  width: 20px;
  height: 15px;
  z-index: 1;
  border: 1px solid red; /* for visualization only */
  pointer-events:initial;

}

.overflow:hover:after{
  cursor: pointer;
}

.tooltip {
  /* visibility: hidden; */
  display: none;
  position: absolute;
  top: 10;
  left: 0;
  background-color: #fff;
  padding: 10px;
  -webkit-box-shadow: 0 0 50px 0 rgba(0,0,0,0.3);
  opacity: 0;
  transition: opacity 0.5s ease;
}


.overflow:hover + .tooltip {
  /*visibility: visible; */
  display: initial;
  transition: opacity 0.5s ease;
  opacity: 1;
}
<div>
  <span class="wrap">
    <span class="overflow" style="float: left; width: 50px">Long text that might overflow</span>
    <span class='tooltip'>Long text that might overflow.</span>
  </span>
  Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad recusandae perspiciatis accusantium quas aut explicabo ab. Doloremque quam eos, alias dolore, iusto pariatur earum, ullam, quidem dolores deleniti perspiciatis omnis.
</div>

Hannes
la source
Ceci est exactement ce que je cherchais!
Praneet Nadkar
Excellente solution, exactement ce dont j'ai besoin. Je vous remercie !
Agu V
17

Voici mon plugin jQuery:

(function($) {
    'use strict';
    $.fn.tooltipOnOverflow = function() {
        $(this).on("mouseenter", function() {
            if (this.offsetWidth < this.scrollWidth) {
                $(this).attr('title', $(this).text());
            } else {
                $(this).removeAttr("title");
            }
        });
    };
})(jQuery);

Usage:

$("td, th").tooltipOnOverflow();

Éditer:

J'ai fait un aperçu de ce plugin. https://gist.github.com/UziTech/d45102cdffb1039d4415

Tony Brix
la source
1
Cela ne fonctionne pas sur IE10 - offsetWidth est identique à scrollWidth, les deux me donnant la largeur tronquée.
SsjCosty
@SsjCosty pourquoi testez-vous sur IE 10? Personne ne devrait utiliser IE 10 à ce stade, car quiconque peut installer IE 10 peut installer IE 11.
Tony Brix
Eh bien, nous avons une politique d'entreprise selon laquelle notre version minimale d'IE prise en charge est 10. Aussi parce que beaucoup de nos clients (certaines banques et diverses institutions) utilisent encore IE10. Tant pis.
SsjCosty
12

Nous devons détecter si les points de suspension sont réellement appliqués, puis afficher une info-bulle pour révéler le texte intégral. Il ne suffit pas de comparer " this.offsetWidth < this.scrollWidth" lorsque l'élément contient presque son contenu, mais il ne manque qu'un ou deux pixels de plus en largeur, en particulier pour le texte de caractères chinois / japonais / coréens pleine largeur.

Voici un exemple: http://jsfiddle.net/28r5D/5/

J'ai trouvé un moyen d'améliorer la détection des points de suspension:

  1. Comparez d'abord " this.offsetWidth < this.scrollWidth", continuez l'étape 2 en cas d'échec.
  2. Basculez temporairement le style CSS en {'overflow': 'visible', 'white-space': 'normal', 'word-break': 'break-all'}.
  3. Laissez le navigateur relayer. Si le retour à la ligne se produit, l'élément augmente sa hauteur, ce qui signifie également que des points de suspension sont requis.
  4. Restaurer le style CSS.

Voici mon amélioration: http://jsfiddle.net/28r5D/6/

Feng Dihai
la source
10

J'ai créé un plugin jQuery qui utilise l'info-bulle de Bootstrap au lieu de l'info-bulle intégrée du navigateur. Veuillez noter que cela n'a pas été testé avec un navigateur plus ancien.

JSFiddle: https://jsfiddle.net/0bhsoavy/4/

$.fn.tooltipOnOverflow = function(options) {
    $(this).on("mouseenter", function() {
    if (this.offsetWidth < this.scrollWidth) {
        options = options || { placement: "auto"}
        options.title = $(this).text();
      $(this).tooltip(options);
      $(this).tooltip("show");
    } else {
      if ($(this).data("bs.tooltip")) {
        $tooltip.tooltip("hide");
        $tooltip.removeData("bs.tooltip");
      }
    }
  });
};
Steven
la source
Simple et brillant. Je vous remercie!
GumZ
3

C'est ce que j'ai fait. La plupart des scripts d'infobulles nécessitent que vous exécutiez une fonction qui stocke les info-bulles. Ceci est un exemple jQuery:

$.when($('*').filter(function() {
   return $(this).css('text-overflow') == 'ellipsis';
}).each(function() {
   if (this.offsetWidth < this.scrollWidth && !$(this).attr('title')) {
      $(this).attr('title', $(this).text());
   }
})).done(function(){ 
   setupTooltip();
});

Si vous ne vouliez pas vérifier les ellipses css, vous pouvez simplifier comme:

$.when($('*').filter(function() {
   return (this.offsetWidth < this.scrollWidth && !$(this).attr('title'));
}).each(function() {
   $(this).attr('title', $(this).text());
})).done(function(){ 
   setupTooltip();
});

J'ai le "quand" autour de lui, de sorte que la fonction "setupTooltip" ne s'exécute pas tant que tous les titres n'ont pas été mis à jour. Remplacez le "setupTooltip", par votre fonction d'info-bulle et le * par les éléments que vous souhaitez vérifier. * passera par tous si vous le laissez.

Si vous souhaitez simplement mettre à jour les attributs de titre avec l'info-bulle des navigateurs, vous pouvez simplifier comme:

$('*').filter(function() {
   return $(this).css('text-overflow') == 'ellipsis';
}).each(function() {
   if (this.offsetWidth < this.scrollWidth && !$(this).attr('title')) {
      $(this).attr('title', $(this).text());
   }
});

Ou sans vérification des points de suspension:

$.when($('*').filter(function() {
   return (this.offsetWidth < this.scrollWidth && !$(this).attr('title'));
}).each(function() {
   $(this).attr('title', $(this).text());
});
fanfavorite
la source
Pourquoi le downvote? Veuillez commenter si vous le faites afin que les gens sachent quel est le problème. Merci.
fanfavorite
2

Si vous souhaitez le faire uniquement en utilisant javascript, je ferais ce qui suit. Donnez à l'intervalle un attribut id (afin qu'il puisse être facilement récupéré à partir du DOM) et placez tout le contenu dans un attribut nommé «contenu»:

<span id='myDataId' style='text-overflow: ellipsis; overflow : hidden;
 white-space: nowrap; width: 71;' content='{$myData}'>${myData}</span>

Ensuite, dans votre javascript, vous pouvez effectuer les opérations suivantes après que l'élément a été inséré dans le DOM.

var elemInnerText, elemContent;
elemInnerText = document.getElementById("myDataId").innerText;
elemContent = document.getElementById("myDataId").getAttribute('content')
if(elemInnerText.length <= elemContent.length)
{
   document.getElementById("myDataId").setAttribute('title', elemContent); 
}

Bien sûr, si vous utilisez javascript pour insérer la plage dans le DOM, vous pouvez simplement conserver le contenu dans une variable avant de l'insérer. De cette façon, vous n'avez pas besoin d'un attribut de contenu sur la durée.

Il existe des solutions plus élégantes que cela si vous souhaitez utiliser jQuery.

Mark Costello
la source
Agréable. Puisque j'utilise JQuery dans une autre partie de cette page - quelle est la solution élégante de JQuery ??
Spiderman
$("#myDataId").attr("title", function() { var innerText = $(this).text(); var content = $(this).attr("content"); if (innerText.length <= content.length) { return content; } return null; });
Mark Costello
1
J'ai essayé votre première suggestion avec du javascript pur - cela ne fonctionne pas. la propriété 'innerText' enregistre la longueur complète du texte même s'il ne s'affiche pas complètement à l'écran, donc toujours: elemInnerText.length == elemContent.length !!
Spiderman
Le style de votre envergure est la largeur 71. Pourquoi ne pas vérifier si la largeur de la chaîne est plus longue que cela? Si vous vouliez faire quelque chose de vraiment funky, vous pourriez ajouter un élément caché sans le texte-overflow: ellipsis défini et comparer les largeurs des deux. Si celui qui est caché est plus large que celui qui n'est pas caché, ajoutez un attribut title à celui qui est visible.
Mark Costello
2

J'ai une classe CSS, qui détermine où placer les points de suspension. Sur cette base, je fais ce qui suit (l'ensemble d'éléments pourrait être différent, j'écris ceux-ci, où les points de suspension sont utilisés, bien sûr, il pourrait s'agir d'un sélecteur de classe distinct):

$(document).on('mouseover', 'input, td, th', function() {
    if ($(this).css('text-overflow') && typeof $(this).attr('title') === 'undefined') {
        $(this).attr('title', $(this).val());
    }
});
Alexandre
la source
1
i mon cas thisétait un anchordonc j'ai utilisé à la this.text()place deval()
Basheer AL-MOMANI
2

Voici une solution JavaScript Vanilla:

(function init() {

  var cells = document.getElementsByClassName("cell");

  for(let index = 0; index < cells.length; ++index) {
    let cell = cells.item(index);
    cell.addEventListener('mouseenter', setTitleIfNecessary, false);
  }

  function setTitleIfNecessary() {
    if(this.offsetWidth < this.scrollWidth) {
      this.setAttribute('title', this.innerHTML);
    }
  }

})();
.cell {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border: 1px;
  border-style: solid;
  width: 120px; 
}
<div class="cell">hello world!</div>
<div class="cell">hello mars! kind regards, world</div>

wuarmin
la source
1

Ce fut ma solution, fonctionne comme un charme!

    $(document).on('mouseover', 'input, span', function() {
      var needEllipsis = $(this).css('text-overflow') && (this.offsetWidth < this.scrollWidth);
      var hasNotTitleAttr = typeof $(this).attr('title') === 'undefined';
      if (needEllipsis === true) {
          if(hasNotTitleAttr === true){
            $(this).attr('title', $(this).val());
          }
      }
      if(needEllipsis === false && hasNotTitleAttr == false){
        $(this).removeAttr('title');
      }
  });
FarukT
la source
Salut @FarukT, votre solution fonctionne bien pour moi, merci beaucoup. Je veux demander si j'ai deux balises html supposant entrée et étendue et je veux calculer la offsetWidth de ces deux balises en même temps pour faire des calculs arithmétiques en utilisant ces offsetWidth. Comment ferais-je ça?
Kunal Tyagi
Salut @KunalTyagi, vous pouvez le faire avec 2 fonctions séparées, j'ai été écrit un seul avec une entrée et une plage mais vous pouvez répliquer cette fonction 2 fois, par exemple la première fonction avec seulement une entrée et dans la deuxième fonction uniquement la plage. comme ceci: $ (document) .on ('mouseover', 'input', function () {...}); et le second $ (document) .on ('mouseover', 'span', function () {...});
FarukT
0

Aucune des solutions ci-dessus n'a fonctionné pour moi, mais j'ai trouvé une excellente solution. La plus grande erreur que les gens font est d'avoir les 3 propriétés CSS déclarées sur l'élément lors du chargement de page. Vous devez ajouter ces styles + info-bulle dynamiquement SI et UNIQUEMENT SI la plage sur laquelle vous voulez une ellipse est plus large que son parent.

    $('table').each(function(){
        var content = $(this).find('span').text();
        var span = $(this).find('span');
        var td = $(this).find('td');
        var styles = {
            'text-overflow':'ellipsis',
            'white-space':'nowrap',
            'overflow':'hidden',
            'display':'block',
            'width': 'auto'
        };
        if (span.width() > td.width()){
            span.css(styles)
                .tooltip({
                trigger: 'hover',
                html: true,
                title: content,
                placement: 'bottom'
            });
        }
    });
Brian Gabriel
la source
0

Vous pouvez éventuellement entourer la travée d'une autre travée, puis simplement tester si la largeur de la travée d'origine / intérieure est supérieure à celle de la nouvelle travée / extérieure. Notez que je dis peut-être - c'est à peu près basé sur ma situation où j'avais un spanintérieur d'un tddonc je ne sais pas vraiment si cela fonctionnera avec un spanintérieur d'un span.

Voici cependant mon code pour les autres qui peuvent se retrouver dans une position similaire à la mienne; Je le copie / colle sans modification même si c'est dans un contexte angulaire, je ne pense pas que cela nuit à la lisibilité et au concept essentiel. Je l'ai codé comme méthode de service parce que je devais l'appliquer à plus d'un endroit. Le sélecteur que j'ai transmis est un sélecteur de classe qui correspondra à plusieurs instances.

CaseService.applyTooltip = function(selector) {
    angular.element(selector).on('mouseenter', function(){
        var td = $(this)
        var span = td.find('span');

        if (!span.attr('tooltip-computed')) {
            //compute just once
            span.attr('tooltip-computed','1');

            if (span.width() > td.width()){
                span.attr('data-toggle','tooltip');
                span.attr('data-placement','right');
                span.attr('title', span.html());
            }
        }
    });
}
Dexygen
la source