Wykrywanie wielokropka przepełnienia tekstu HTML


176

Mam zbiór elementów blokowych na stronie. Wszystkie mają ustawione odstępy, przepełnienie, przepełnienie tekstu w regułach CSS, dzięki czemu przepełniony tekst jest przycinany i używany jest wielokropek.

Jednak nie wszystkie elementy się przepełniają.

Czy mimo wszystko mogę użyć javascript do wykrycia, które elementy są przepełnione?

Dzięki.

Dodano: przykładowa struktura HTML, z którą pracuję.

<td><span>Normal text</span></td>
<td><span>Long text that will be trimmed text</span></td>

Elementy SPAN zawsze mieszczą się w komórkach, mają zastosowaną regułę wielokropka. Chcę wykryć, kiedy wielokropek jest stosowany do zawartości tekstu SPAN.



10
To nie jest duplikat! To pytanie dotyczy jednego elementu w innym elemencie nadrzędnym. Mówię o tekście w jednym elemencie. W moim przypadku SPAN w TD nigdy nie przepełnia TD, to tekst w SPAN przepełnia się i zostaje przycięty. To właśnie próbuję wykryć! Przepraszam - mogłem lepiej zadać to pytanie, przyznaję.
deanoj

Och, zapomniałem dodać - to musi działać tylko na webkicie, jeśli to pomoże ...
deanoj

Zrobiłem Ghommeya tylko po to, żeby sprawdzić, czy zadziałało ... nie zadziałało.
deanoj

aspekt wielokropka jest nieistotny; wszystko, co musisz wykryć, to czy jest przepełniony.
Spudley,

Odpowiedzi:


114

Kiedyś musiałem to zrobić, a jedynym niezawodnym rozwiązaniem dla różnych przeglądarek, na które natknąłem się, był hack. Nie jestem największym fanem takich rozwiązań, ale z pewnością daje to poprawny wynik za każdym razem.

Chodzi o to, że klonujesz element, usuwasz wszelką szerokość obwiedni i sprawdzasz, czy sklonowany element jest szerszy niż oryginał. Jeśli tak, wiesz, że zostanie obcięty.

Na przykład używając jQuery:

var $element = $('#element-to-test');
var $c = $element
           .clone()
           .css({display: 'inline', width: 'auto', visibility: 'hidden'})
           .appendTo('body');

if( $c.width() > $element.width() ) {
    // text was truncated. 
    // do what you need to do
}

$c.remove();

Zrobiłem jsFiddle, aby to zademonstrować, http://jsfiddle.net/cgzW8/2/

Możesz nawet stworzyć własny niestandardowy pseudo-selektor dla jQuery:

$.expr[':'].truncated = function(obj) {
  var $this = $(obj);
  var $c = $this
             .clone()
             .css({display: 'inline', width: 'auto', visibility: 'hidden'})
             .appendTo('body');

  var c_width = $c.width();
  $c.remove();

  if ( c_width > $this.width() )
    return true;
  else
    return false;
};

Następnie użyj go, aby znaleźć elementy

$truncated_elements = $('.my-selector:truncated');

Demo: http://jsfiddle.net/cgzW8/293/

Miejmy nadzieję, że to pomoże, chociaż jest hacky.


1
@Lauri No; Obcinanie CSS nie zmienia faktycznego tekstu w ramce, więc zawartość jest zawsze taka sama, niezależnie od tego, czy jest obcięta, widoczna czy ukryta. Nadal nie ma sposobu na programowe pobranie obciętego tekstu, gdyby tak było, nie musiałbyś najpierw klonować elementu!
Christian,

1
Wygląda na to, że to nie zadziała w sytuacjach, w których nie ma white-space: nowrap.
Jakub Hampl

2
Muszę powiedzieć, że po przeszukaniu DUŻO w Internecie i próbie wdrożenia wielu rozwiązań, to zdecydowanie najbardziej niezawodne, jakie znalazłem. To rozwiązanie nie daje różnych wyników między przeglądarkami, takimi jak element.innerWidth lub element.OffsetWidth, które mają problem z używaniem marginesów lub dopełnienia. Świetne rozwiązanie, dobra robota.
Napis z

1
Dla mnie to nigdzie nie zadziałało. Nie jestem pewien, jak to zależy od CSS (próbowałem go używać na kontrolkach wejściowych, poniższe rozwiązania działały przynajmniej w Chrome i Firefox (ale nie w IE11)) ...
Alexander

3
Świetne rozwiązanie, szczególnie przy użyciu pseudo-selektora jQuery. Ale czasami może nie działać, ponieważ szerokość jest obliczana nieprawidłowo, jeśli tekst elementu ma inny styl (rozmiar czcionki, odstępy między literami, marginesy elementów wewnętrznych). W takim przypadku zalecałbym dołączenie elementu clone do $ this.parent () zamiast „body”. To da dokładną kopię elementu, a obliczenia będą prawidłowe. W każdym razie dzięki
Alex,

286

Wypróbuj tę funkcję JS, przekazując element span jako argument:

function isEllipsisActive(e) {
     return (e.offsetWidth < e.scrollWidth);
}

10
To powinna być odpowiedź - bardzo elegancka i działa na Chrome i IE (9)
Roy Truelove

14
Ta odpowiedź i odpowiedź Alexa nie będą działać w IE8; są pewne dziwne przypadki, w których szerokość przewijania i szerokość zewnętrzna są takie same ... ale ma wielokropek, a potem są takie same przypadki ... i NIE ma elipsy. Innymi słowy, pierwsze rozwiązanie jest jedynym, które działa we wszystkich przeglądarkach.

3
Dla tych, którzy chcą zrozumieć offsetWidth, scrollWidth i clientWidth, oto bardzo dobre wyjaśnienie: stackoverflow.com/a/21064102/1815779
Linh Dam

4
Na text-overflow:ellipsisnim powinno byće.offsetWidth <= e.scrollWidth
oriadam,

4
Na elastycznych dzieciach są zawsze równi sobie, a zatem nie będą dla nich działać.
Kevin Beal

14

Dodając do odpowiedzi italo, możesz to również zrobić za pomocą jQuery.

function isEllipsisActive($jQueryObject) {
    return ($jQueryObject.width() < $jQueryObject[0].scrollWidth);
}

Ponadto, jak zauważył Smoky, możesz chcieć użyć jQuery externalWidth () zamiast width ().

function isEllipsisActive($jQueryObject) {
    return ($jQueryObject.outerWidth() < $jQueryObject[0].scrollWidth);
}

9

Osoby korzystające (lub planujące użyć) zaakceptowanej odpowiedzi od Christiana Varga powinny mieć świadomość problemów z wydajnością.

Klonowanie / manipulowanie DOM w taki sposób powoduje przepływ DOM (zobacz wyjaśnienie na temat ponownego przepływu DOM tutaj), co jest niezwykle intensywne pod względem zasobów.

Użycie rozwiązania Christiana Vargi na ponad 100 elementach na stronie spowodowało 4-sekundowe opóźnienie ponownego przepływu, podczas którego wątek JS jest blokowany. Biorąc pod uwagę, że JS jest jednowątkowy, oznacza to znaczne opóźnienie UX dla użytkownika końcowego.

Italo Borssatto za odpowiedź powinna być przyjętym jeden, to było około 10 razy szybciej podczas mojego profilowania.


2
Niestety nie działa w każdym scenariuszu (chciałbym, żeby tak było). Uderzenie w wydajność, o którym tutaj wspominasz, można zrównoważyć, uruchamiając go tylko w sytuacji, w której musisz sprawdzić - na przykład tylko w mouseentercelu sprawdzenia, czy należy wyświetlić podpowiedź.
Jacob

5

Odpowiedź od italo jest bardzo dobra! Pozwólcie jednak, że trochę to udoskonalę:

function isEllipsisActive(e) {
   var tolerance = 2; // In px. Depends on the font you are using
   return e.offsetWidth + tolerance < e.scrollWidth;
}

Zgodność z różnymi przeglądarkami

Jeśli faktycznie wypróbujesz powyższy kod i użyjesz console.logdo wydrukowania wartości e.offsetWidthi e.scrollWidth, zauważysz w IE, że nawet jeśli nie masz obcięcia tekstu, występuje różnica wartości 1pxlub 2pxwystępuje.

Tak więc, w zależności od używanego rozmiaru czcionki, dopuszczaj pewną tolerancję!


To nie działa na IE10 - offsetWidth jest identyczny z scrollWidth, oba dają mi obciętą szerokość.
SsjCosty

1
Potrzebuję tej tolerancji również w Chrome 60 (najnowszym).
Jan Żankowski

5

elem.offsetWdith VS ele.scrollWidth Ta praca dla mnie! https://jsfiddle.net/gustavojuan/210to9p1/

$(function() {
  $('.endtext').each(function(index, elem) {
    debugger;
    if(elem.offsetWidth !== elem.scrollWidth){
      $(this).css({color: '#FF0000'})
    }
  });
});

Ani nie działa w aktualnej wersji Chrome ani Firefox. Oba elementy stają się czerwone.
xehpuk

4

Ten przykład pokazuje podpowiedź w tabeli komórek z obciętym tekstem. Jest dynamiczny na podstawie szerokości stołu:

$.expr[':'].truncated = function (obj) {
    var element = $(obj);

    return (element[0].scrollHeight > (element.innerHeight() + 1)) || (element[0].scrollWidth > (element.innerWidth() + 1));
};

$(document).ready(function () {
    $("td").mouseenter(function () {
        var cella = $(this);
        var isTruncated = cella.filter(":truncated").length > 0;
        if (isTruncated) 
            cella.attr("title", cella.text());
        else 
            cella.attr("title", null);
    });
});

Próbny: https://jsfiddle.net/t4qs3tqs/

Działa na wszystkich wersjach jQuery


1

Myślę, że lepszym sposobem na wykrycie tego jest użycie getClientRects(), wydaje się, że każdy prostokąt ma tę samą wysokość, więc możemy obliczyć liczbę linii z liczbą różnychtop wartości.

getClientRectspracować w ten sposób

function getRowRects(element) {
    var rects = [],
        clientRects = element.getClientRects(),
        len = clientRects.length,
        clientRect, top, rectsLen, rect, i;

    for(i=0; i<len; i++) {
        has = false;
        rectsLen = rects.length;
        clientRect = clientRects[i];
        top = clientRect.top;
        while(rectsLen--) {
            rect = rects[rectsLen];
            if (rect.top == top) {
                has = true;
                break;
            }
        }
        if(has) {
            rect.right = rect.right > clientRect.right ? rect.right : clientRect.right;
            rect.width = rect.right - rect.left;
        }
        else {
            rects.push({
                top: clientRect.top,
                right: clientRect.right,
                bottom: clientRect.bottom,
                left: clientRect.left,
                width: clientRect.width,
                height: clientRect.height
            });
        }
    }
    return rects;
}

getRowRects działa jak ten sposób

można wykryć jak to


1

Wszystkie rozwiązania tak naprawdę u mnie nie działały, zadziałało porównanie elementów scrollWidthzscrollWidth jego rodzica (lub dziecka, w zależności od tego, który element ma wyzwalacz).

Kiedy dziecko scrollWidthjest wyżej niż jego rodzice, oznacza to, że .text-ellipsisjest aktywne.


Kiedy eventjest elementem nadrzędnym

function isEllipsisActive(event) {
    let el          = event.currentTarget;
    let width       = el.offsetWidth;
    let widthChild  = el.firstChild.offsetWidth;
    return (widthChild >= width);
}

Kiedy eventjest elementem potomnym

function isEllipsisActive(event) {
    let el          = event.currentTarget;
    let width       = el.offsetWidth;
    let widthParent = el.parentElement.scrollWidth;
    return (width >= widthParent);
}

0

Plik e.offsetWidth < e.scrollWidthRozwiązanie nie zawsze działa.

A jeśli chcesz używać czystego JavaScript, polecam użyć tego:

(maszynopis)

public isEllipsisActive(element: HTMLElement): boolean {
    element.style.overflow = 'initial';
    const noEllipsisWidth = element.offsetWidth;
    element.style.overflow = 'hidden';
    const ellipsisWidth = element.offsetWidth;

    if (ellipsisWidth < noEllipsisWidth) {
      return true;
    } else {
      return false;
    }
}

0

Rozwiązanie @ItaloBorssatto jest idealne. Ale zanim spojrzałem na SO - podjąłem decyzję. Oto jest :)

const elems = document.querySelectorAll('span');
elems.forEach(elem => {
  checkEllipsis(elem);
});

function checkEllipsis(elem){
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const styles = getComputedStyle(elem);
  ctx.font = `${styles.fontWeight} ${styles.fontSize} ${styles.fontFamily}`;
  const widthTxt = ctx.measureText(elem.innerText).width;
  if (widthTxt > parseFloat(styles.width)){
    elem.style.color = 'red'
  }
}
span.cat {
    display: block;
    border: 1px solid black;
    white-space: nowrap;
    width: 100px;
    overflow: hidden;
    text-overflow: ellipsis;
}
 <span class="cat">Small Cat</span>
      <span class="cat">Looooooooooooooong Cat</span>


0

Moja realizacja)

const items = Array.from(document.querySelectorAll('.item'));
items.forEach(item =>{
    item.style.color = checkEllipsis(item) ? 'red': 'black'
})

function checkEllipsis(el){
  const styles = getComputedStyle(el);
  const widthEl = parseFloat(styles.width);
  const ctx = document.createElement('canvas').getContext('2d');
  ctx.font = `${styles.fontSize} ${styles.fontFamily}`;
  const text = ctx.measureText(el.innerText);
  return text.width > widthEl;
}
.item{
  width: 60px;
  overflow: hidden;
  text-overflow: ellipsis;
}
      <div class="item">Short</div>
      <div class="item">Loooooooooooong</div>


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.