Przepełnienie wielowierszowego tekstu w różnych przeglądarkach z wielokropkiem dołączonym w ramach stałej szerokości i wysokości

178

Zrobiłem zdjęcie na to pytanie, aby było łatwiejsze do zrozumienia.

Czy można utworzyć wielokropek o <div>stałej szerokości i wielu liniach?

przepełnienie tekstu

Wypróbowałem kilka wtyczek jQuery tu i tam, ale nie mogę znaleźć tego, którego szukam. Jakieś rekomendacje? Pomysły?

Edward
źródło
1
i stackoverflow.com/questions/3922739/… dla rozwiązania samego css
Evgeny
powiązany artykuł css-tricks.com/line-clampin
Adrien Be
2
Dla każdego, kto szuka tego w połowie 2016 roku, krótka odpowiedź brzmi: NIE, nie jest to możliwe w eleganckim, cross-browser, tylko w CSS. Rozwiązanie często podawane jako najbliższe ukończenia ( codepen.io/romanrudenko/pen/ymHFh ) jest tak Goldbergowskie, że powoduje ból całego ciała i wciąż jest dość brzydkie.
konrad

Odpowiedzi:

91

Tylko krótki podstawowy pomysł.

Testowałem z następującymi znacznikami:

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

I CSS:

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

Zastosowanie tego jQuery przyniesie pożądany rezultat:

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

Wielokrotnie próbuje usunąć ostatnie słowo z tekstu, aż osiągnie żądany rozmiar. Z powodu przepełnienia: ukryty; proces pozostaje niewidoczny i nawet przy wyłączonym JS wynik pozostaje „wizualnie poprawny” (oczywiście bez „…”).

Jeśli połączysz to z rozsądnym obcięciem po stronie serwera (co pozostawi tylko niewielki narzut), to będzie działać szybciej :).

Ponownie, nie jest to kompletne rozwiązanie, tylko pomysł.

AKTUALIZACJA: Dodano demo jsFiddle .

kapa
źródło
1
świetne rozwiązanie @bazmegakapa ... ale mam pewne problemy podczas próby dostosowania go do mojego przypadku. Mam różne, lia wewnątrz każdego z nich jest .blocki a .block h2i muszę zastosować to do h2środka, .blockale nie mogłem zmusić go do pracy. Czy jest inaczej, jeśli jest ich więcej niż jeden .block h2?
Alex
1
W moim przypadku pozostawiły tylko 2 wiersze tekstu, podczas gdy powinno być 3. Najwyraźniej mój kontener był mniejszy niż wiersz height*3o kilka pikseli. Łatwym rozwiązaniem jest po prostu dodanie kilku pikseli dodivh
Lukas LT
3
Miałem złe doświadczenia z nieskończoną pętlą w tym skrypcie, ponieważ tekst zawierał tylko jedno bardzo długie słowo, więc wyrażenie regularne zastępowania nigdy nie zostało dopasowane. Aby tego uniknąć, dodaj ten kod zaraz po whilewierszu:if(!$p.text().match(/\W*\s(\S)*$/)) break;
KrisWebDev
1
Chociaż prawdopodobnie nie będzie to problemem w tym przypadku, wielokrotne aktualizowanie DOM i sprawdzanie układu jest złym pomysłem, ponieważ może spowodować spowolnienie. Aby to złagodzić, możesz coś podobnego do wyszukiwania binarnego: przetestuj, czy blok tekstu już pasuje, w przeciwnym razie podziel tekst na słowa lub znaki i zdefiniuj granice (dolne = 1 słowo / znaki, górne = wszystkie słowa / znaki) , 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;}}. Jak znalazł @KrisWebDev, będziesz chciał również sprawdzić jedno wielkie słowo.
Chinoto Vokro
1
To świetne rozwiązanie. W moim przypadku muszę śledzić oryginalny tekst, aby móc responsywnie skracać pełną wartość tekstową. Kiedy więc strona się ładuje, zapisuję oryginalny tekst w zmiennej i zanim uruchomię tę logikę, upewniam się, że „odświeżę” element oryginalną wartością tekstową. Dodaj odbicie i działa cudownie.
besseddrest
68

Wypróbuj wtyczkę jQuery.dotdotdot .

$(".ellipsis").dotdotdot();
Matt
źródło
11
Jak byś to powiedział? kropka-kropka-kropka?
JackAce
58
Naprawdę do bani używać> 600 linii js do rozwiązania problemu, który powinien rozwiązać css
Jethro Larson,
Próbowałem i działa dobrze. Powinna być zaakceptowana odpowiedź
AbdelHady
1
Działa, ale upewnij się, że używasz zdarzenia window.loaded, a nie $ (document) .ready (), ponieważ czcionki i inne zasoby zewnętrzne mogą wpływać na układ HTML. Jeśli dotdotdot zostanie wykonany przed załadowaniem tych zasobów, tekst nie zostanie obcięty w prawidłowej pozycji.
sboisse
10
Jest to narzędzie komercyjne, kosztuje 5 USD za jedną witrynę i 35 USD za wiele witryn. Licencjonowanie byłoby uciążliwe. Myślałem, że jest darmowy i można go od razu zintegrować, nie tak!
gen b.
29

Biblioteki JavaScript do „blokowania linii”

Zwróć uwagę, że „zaciskanie linii” jest również określane jako „elipsa na bloku wielu linii” lub „pionowa elipsa”.


github.com/BeSite/jQuery.dotdotdot


github.com/josephschmitt/Clamp.js


Oto kilka innych, których jeszcze nie zbadałem:


Rozwiązania CSS do mocowania przewodów

Istnieje kilka rozwiązań CSS, ale najprostsze zastosowania, -webkit-line-clampktóre mają słabą obsługę przeglądarki . Zobacz demo na żywo na jsfiddle.net/AdrienBe/jthu55v7/

Wiele osób dołożyło wszelkich starań, aby to się stało, używając tylko CSS. Zobacz artykuły i pytania na ten temat:


Co bym polecił

Nie komplikuj. Jeśli nie masz dużo czasu, aby poświęcić się tej funkcji, wybierz najprostsze i przetestowane rozwiązanie: prosty CSS lub dobrze przetestowaną bibliotekę javascript.

Wybierz coś wymyślnego / złożonego / wysoce spersonalizowanego, a zapłacisz za to cenę w przyszłości.


Co robią inni

Zanikanie, tak jak robi to Airbnb, może być dobrym rozwiązaniem. Prawdopodobnie jest to podstawowy CSS połączony z podstawowym jQuery. Właściwie wydaje się bardzo podobny do tego rozwiązania w CSSTricks

Rozwiązanie AirBnb „czytaj więcej”

Aha, a jeśli szukasz inspiracji projektowych:

Adrien Be
źródło
6

W HTML nie ma takiej funkcji i jest to bardzo frustrujące.

Opracowałem bibliotekę, aby sobie z tym poradzić.

  • Przepełnienie tekstu w wielu wierszach: wielokropek
  • Tekst wielowierszowy z technologiami, które go nie obsługują: na przykład SVG, Canvas
  • Miej dokładnie takie same podziały wierszy w tekście SVG, w renderowaniu HTML i na przykład w eksporcie PDF

Sprawdź moją witrynę, aby znaleźć zrzut ekranu, samouczek i łącze do pobrania.

Samuel Rossille
źródło
„błąd podczas nawiązywania połączenia z bazą danych” ... możesz chcieć robić to jak wszyscy i hostować swój projekt na Github, prawdopodobnie byłoby to przyjemniejsze dla Ciebie i społeczności :)
Adrien Be
@AdrienBe jest na Github: github.com/rossille/jstext , i masz rację, github jest bardziej stabilny niż moja witryna, ustawiłem stronę github jako główny link
Samuel Rossille
@SamuelRossille wspaniała wiadomość, dzięki za szybką aktualizację!
Adrien Be
4

Czyste rozwiązanie JS oparte na rozwiązaniu bažmegakapy i pewne porządki uwzględniające ludzi, którzy próbują podać wysokość / maksymalną wysokość mniejszą niż linia elementu Wysokość:

  var truncationEl = document.getElementById('truncation-test');
  function calculateTruncation(el) {
    var text;
    while(el.clientHeight < el.scrollHeight) {
      text = el.innerHTML.trim();
      if(text.split(' ').length <= 1) {
        break;
      }
      el.innerHTML = text.replace(/\W*\s(\S)*$/, '...');
    }
  }

  calculateTruncation(truncationEl);
prashn64
źródło
To jest bardzo nieefektywny kod. Nawiasem mówiąc, użycie wyglądu "while" jest potencjalnym błędem w nieskończonej pętli.
WebBrother
4

Mam rozwiązanie, które działa dobrze, ale zamiast wielokropka używa gradientu. Zaletą jest to, że nie musisz wykonywać żadnych obliczeń JavaScript i działa to w przypadku kontenerów o zmiennej szerokości, w tym komórek tabel. Używa kilku dodatkowych elementów div, ale jest bardzo łatwy do zaimplementowania.

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

Edycja: Przepraszamy, nie wiedziałem, że link nie wystarczy. Rozwiązaniem jest umieszczenie elementu DIV wokół tekstu i nadanie mu stylu, aby kontrolować przepełnienie. Wewnątrz elementu div umieść kolejny element div z gradientem „zanikania”, który można utworzyć za pomocą CSS lub obrazu (dla starego IE). Gradient przechodzi od koloru przezroczystego do koloru tła komórki tabeli i jest nieco szerszy niż wielokropek. Jeśli tekst jest długi i przepełniony, przechodzi pod element div „fade” i wygląda na „wyblakły”. Jeśli tekst jest krótki, zanikanie jest niewidoczne, więc nie ma problemu. Oba kontenery można dostosować, aby wyświetlać jeden lub wiele wierszy, ustawiając wysokość kontenera jako wielokrotność wysokości wiersza tekstu. Element div „fade” można ustawić tak, aby obejmował tylko ostatnią linię.

Miriam Salzer
źródło
Prosimy o udostępnienie ważnych części rozwiązania, odpowiedzi dotyczące tylko łącza SO są niedozwolone.
kapa
genialnym aspektem tego jest to, że sam tekst nie jest obcięty, więc jeśli użytkownik skopiuje i wklei tabelę, pojawi się cała zawartość.
prototyp
Bardzo fajna koncepcja. Jest to również wspomniane w tym artykule (sposób „zanikania”). Myślę, że css-tricks.com/line-clampin
Adrien Be
4

Oto prosty sposób CSS, aby to osiągnąć: http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/

Oto podsumowanie:

wprowadź opis obrazu tutaj

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

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

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

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

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

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

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

        text-align: right;

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

Możesz używać -webkit-line-clampproperty z div.

-webkit-line-clamp: <integer>co oznacza ustawienie maksymalnej liczby wierszy przed obcięciem zawartości, a następnie wyświetlenie wielokropka (…)na końcu ostatniej linii.

div {
  width: 205px;
  height: 40px;
  background-color: gainsboro;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  
  /* <integer> values */
  -webkit-line-clamp: 2;
}
<div>This is a multi-lines text block, some lines inside the div, while some outside</div>

Penny Liu
źródło
2
Nie wiem, dlaczego ktoś odrzucił tę odpowiedź. Obsługa przeglądarek od marca 2020 r. Jest całkiem przyzwoita - 95% caniuse.com/#search=line-clamp
Julian
2

Oto proste rozwiązanie JavaScript, którego możesz użyć w mgnieniu oka:

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

Możesz się nim bawić w polu kodowym poniżej. Spróbuj zmienić rozmiar czcionki w panelu CSS i dokonaj niewielkiej zmiany w panelu HTML (np. Dodaj gdzieś dodatkową spację), aby zaktualizować wyniki. Niezależnie od rozmiaru czcionki, środkowy akapit powinien być zawsze obcinany do liczby wierszy w drugim parametrze przekazanym do limitLines ().

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

thdoan
źródło
2

EDYCJA: Przeszedł przez Shave, który jest wtyczką JS, która naprawdę dobrze wykonuje wielowierszowe obcinanie tekstu na podstawie podanej maksymalnej wysokości. Używa wyszukiwania binarnego, aby znaleźć optymalny punkt przerwania. Zdecydowanie warte zbadania.


ORYGINALNA ODPOWIEDŹ:

Musiałem wymyślić waniliowe rozwiązanie JS dla tego problemu. W przypadku, nad którym pracowałem, musiałem dopasować długą nazwę produktu do ograniczonej szerokości i ponad dwiema liniami; w razie potrzeby obcinane wielokropkiem.

Użyłem odpowiedzi z różnych postów SO, aby ugotować coś, co pasowało do moich potrzeb. Strategia jest następująca:

  1. Oblicz średnią szerokość znaków wariantu czcionki dla żądanego rozmiaru czcionki.
  2. Oblicz szerokość pojemnika
  3. Oblicz liczbę znaków, które mieszczą się w jednym wierszu kontenera
  4. Oblicz liczbę znaków, do których ma zostać obcięty ciąg, na podstawie liczby znaków mieszczących się w wierszu oraz liczby wierszy, którymi ma się zawijać tekst.
  5. Obetnij tekst wejściowy na podstawie poprzedniego obliczenia (uwzględniając dodatkowe znaki dodane wielokropkiem) i dodaj „...” na końcu

Przykład kodu:

/**
 * 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. Jeśli obcięcie ma znajdować się tylko w jednym wierszu, czysta metoda CSS polegająca na przepełnieniu tekstu: wielokropek jest schludniejszy
  2. Czcionki, które nie mają stałej szerokości, mogą powodować zbyt wczesne lub późne obcięcie (ponieważ różne znaki mają różne szerokości). Użycie parametru pad pomaga w niektórych przypadkach to złagodzić, ale nie będzie niezawodne :)
  3. Dodam linki i odniesienia do oryginalnych postów po odzyskaniu laptopa (historia potrzeb)

PPS: Właśnie zdałem sobie sprawę, że jest to bardzo podobne do podejścia sugerowanego przez @DanMan i @ st.never. Sprawdź fragmenty kodu, aby zapoznać się z przykładem implementacji.

Chirag Ravindra
źródło
2

Bardzo proste rozwiązanie javascript. Elementy Div muszą być stylizowane np .:

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

I 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 + "…";
  }
}
Michał Politzer
źródło
1

Nie jest to dokładna odpowiedź na pytanie, ale natknąłem się na tę stronę, próbując zrobić coś bardzo podobnego, ale chcąc dodać link do „zobacz więcej”, a nie tylko prostą wielokropek. To jest funkcja jQuery, która doda link „więcej” do tekstu, który przepełnia kontener. Osobiście używam tego z Bootstrapem, ale oczywiście będzie działać bez.

Przykład więcej zrzutów ekranu

Aby użyć, umieść tekst w kontenerze w następujący sposób:

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

Po dodaniu poniższej funkcji jQuery wszystkie elementy div, które są większe niż wartość Adjustheight, zostaną obcięte i dodane zostanie łącze „Więcej”.

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

Na tej podstawie, ale zaktualizowane: http://shakenandstirredweb.com/240/jquery-moreless-text

Andy Beverley
źródło
<sigh> Pomyślałem, że ktoś może to zlekceważyć, prawdopodobnie dlatego, że nie jest to dokładna odpowiedź na pytanie. Niemniej mam nadzieję, że komuś się przyda, bo nigdzie indziej nie mogłem tej informacji i na tym zakończyłem poszukiwania.
Andy Beverley,
1

Wspomniana wtyczka dotdotdot jQuery działa dobrze z 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);

Odpowiedni znacznik wyglądałby tak:

<p app-ellipsis>{{ selectedItem.Description }}</p>
Edward Olamisan
źródło
1

Demo czystego JS (bez jQuery i pętli 'while')

Kiedy szukałem rozwiązania problemu wielowierszowego wielokropka, zdziwiłem się, że nie ma dobrego bez jQuery. Istnieje również kilka rozwiązań opartych na pętli „while”, ale myślę, że nie są one skuteczne i niebezpieczne ze względu na możliwość wpadnięcia w nieskończoną pętlę. Więc napisałem ten kod:

function ellipsizeTextBox(el) {
  if (el.scrollHeight <= el.offsetHeight) {
    return;
  }

  let wordArray = el.innerHTML.split(' ');
  const wordsLength = wordArray.length;
  let activeWord;
  let activePhrase;
  let isEllipsed = false;

  for (let i = 0; i < wordsLength; i++) {
    if (el.scrollHeight > el.offsetHeight) {
      activeWord = wordArray.pop();
      el.innerHTML = activePhrase = wordArray.join(' ');
    } else {
      break;
    }
  }

  let charsArray = activeWord.split('');
  const charsLength = charsArray.length;

  for (let i = 0; i < charsLength; i++) {
    if (el.scrollHeight > el.offsetHeight) {
      charsArray.pop();
      el.innerHTML = activePhrase + ' ' + charsArray.join('')  + '...';
      isEllipsed = true;
    } else {
      break;
    }
  }

  if (!isEllipsed) {
    activePhrase = el.innerHTML;

    let phraseArr = activePhrase.split('');
    phraseArr = phraseArr.slice(0, phraseArr.length - 3)
    el.innerHTML = phraseArr.join('') + '...';
  }
}

let el = document.getElementById('ellipsed');

ellipsizeTextBox(el);
WebBrother
źródło
1

Może dość późno, ale używając SCSS możesz zadeklarować taką funkcję jak:

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

I używaj go jak:

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

Który obetnie tekst do jednej linii i wiedząc, że ma 1,4 jego wysokości. Oczekiwany wynik to chrome do renderowania ...na końcu i FF z pewnym chłodnym zanikaniem na końcu

Firefox

wprowadź opis obrazu tutaj

Chrom

wprowadź opis obrazu tutaj

Alexis Duran
źródło
1

W odpowiedzi Adrien Be znalazłem to krótkie rozwiązanie zawierające tylko CSS :

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

Od marca 2020 r. Obsługa przeglądarek wynosi 95,3% i nie jest obsługiwana w IE i Opera Mini. Działa na Chrome, Safari, Firefox i Edge.

Julian
źródło
0

Prawdopodobnie nie możesz tego zrobić (obecnie?) Bez czcionki o stałej szerokości, takiej jak Courier. W przypadku czcionki o stałej szerokości każda litera zajmuje tę samą przestrzeń w poziomie, więc prawdopodobnie można policzyć litery i pomnożyć wynik przez bieżący rozmiar czcionki w ems lub exs. Następnie wystarczyłoby sprawdzić, ile liter mieści się w jednym wierszu, a następnie podzielić.

Alternatywnie, w przypadku czcionek bez stałej liczby znaków, możesz utworzyć mapowanie dla wszystkich możliwych znaków (np. I = 2px, m = 5px), a następnie wykonać obliczenia. Ale dużo brzydkiej pracy.

DanMan
źródło
0

Aby rozwinąć rozwiązanie @ DanMan: w przypadku, gdy używane są czcionki o zmiennej szerokości, można użyć średniej szerokości czcionki. Wiąże się to z dwoma problemami: 1) tekst ze zbyt wieloma literami W byłby przepełniony, a 2) tekst ze zbyt dużą liczbą znaków I zostałby wcześniej obcięty.

Możesz też przyjąć najgorsze podejście i użyć szerokości litery „W” (która moim zdaniem jest najszersza). To usuwa problem 1 powyżej, ale nasila problem 2.

Innym podejściem mogłoby być: pozostawienie overflow: clipw div i dodanie sekcji wielokropka (może innego div lub obraz) z float: right; position: relative; bottom: 0px;(niesprawdzone). Sztuczka polega na tym, aby obraz pojawił się nad końcem tekstu.

Możesz również wyświetlić obraz tylko wtedy, gdy wiesz, że będzie przepełniony (powiedzmy po około 100 znakach)

św. nigdy
źródło
Co to jest overflow: clip? A czego można się spodziewać po tym CSS float?
kapa
0

W przypadku tego kodu nie ma potrzeby stosowania dodatkowego opakowującego elementu div, jeśli element ma wysokość ograniczoną stylem 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);
});

Zapisuje również oryginalny tekst w atrybucie danych, który może być wyświetlany przy użyciu tylko stylów, np. po najechaniu myszą:

.ellipsis-lastline {
    max-height: 5em;
}
.ellipsis-lastline:before {
    content: attr(data-original-content);
    position: absolute;
    display: none;
}
.ellipsis-lastline:hover:before {
    display: block;
}
Johan
źródło
1
To często nieskończona pętla.
Atadj
0

W moim scenariuszu nie mogłem uruchomić żadnej z wyżej wymienionych funkcji, a także musiałem powiedzieć funkcji, ile wierszy ma pokazać, niezależnie od rozmiaru czcionki lub rozmiaru kontenera.

Oparłem rozwiązanie na wykorzystaniu Canvas.measureText metody (whic stanowi HTML5 funkcja) jak wyjaśniono tutaj przez Domi , więc nie jest całkowicie cross-browser.

Możesz zobaczyć, jak to działa na tych skrzypcach .

To jest kod:

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

A kod HTML do użycia wyglądałby tak:

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

Kod do uzyskania rodziny czcionek jest dość prosty iw moim przypadku działa, ale w przypadku bardziej złożonych scenariuszy może być konieczne użycie czegoś podobnego do tych linii .

Również w moim przypadku mówię funkcji, ile wierszy ma użyć, ale możesz obliczyć, ile wierszy ma zostać wyświetlonych zgodnie z rozmiarem kontenera i czcionką.

Dzyann
źródło
0

Stworzyłem wersję, która pozostawia nienaruszony kod HTML. przykład 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;
}

przykład jsfiddle

łał
źródło
0

Napisałem komponent kątowy, który rozwiązuje problem. Dzieli dany tekst na elementy span. Po wyrenderowaniu usuwa wszystkie przepełnione elementy i umieszcza elipsę zaraz po ostatnim widocznym elemencie.

Przykład użycia:

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

Demo Stackblitz: https://stackblitz.com/edit/angular-wfdqtd

Składnik:

import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef, HostListener,
  Input,
  OnChanges,
  ViewChild
} from '@angular/core';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'app-text-overflow-ellipsis',
  template: `
    <span *ngFor="let word of words; let i = index" [innerHTML]="word + (!endsWithHyphen(i) ? ' ' : '')"></span>
    <span #ellipsis [hidden]="!showEllipsis && !initializing" [class.initializing]="initializing" [innerHTML]="'...' + (initializing ? '&nbsp;' : '')"></span>
  `,
  styles: [`
    :host {
      display: block; 
      position: relative;
    }
    .initializing {
      opacity: 0;
    }
  `
  ]
})

export class TextOverflowEllipsisComponent implements OnChanges {
  @Input()
  text: string;

  showEllipsis: boolean;
  initializing: boolean;

  words: string[];

  @ViewChild('ellipsis')
  ellipsisElement: ElementRef;

  constructor(private element: ElementRef, private cdRef: ChangeDetectorRef) {}

  ngOnChanges(){
    this.init();
  }

  @HostListener('window:resize')
  init(){
    // add space after hyphens
    let text = this.text.replace(/-/g, '- ') ;

    this.words = text.split(' ');
    this.initializing = true;
    this.showEllipsis = false;
    this.cdRef.detectChanges();

    setTimeout(() => {
      this.initializing = false;
      let containerElement = this.element.nativeElement;
      let containerWidth = containerElement.clientWidth;
      let wordElements = (<HTMLElement[]>Array.from(containerElement.childNodes)).filter((element) =>
        element.getBoundingClientRect && element !== this.ellipsisElement.nativeElement
      );
      let lines = this.getLines(wordElements, containerWidth);
      let indexOfLastLine = lines.length - 1;
      let lineHeight = this.deductLineHeight(lines);
      if (!lineHeight) {
        return;
      }
      let indexOfLastVisibleLine = Math.floor(containerElement.clientHeight / lineHeight) - 1;

      if (indexOfLastVisibleLine < indexOfLastLine) {

        // remove overflowing lines
        for (let i = indexOfLastLine; i > indexOfLastVisibleLine; i--) {
          for (let j = 0; j < lines[i].length; j++) {
            this.words.splice(-1, 1);
          }
        }

        // make ellipsis fit into last visible line
        let lastVisibleLine = lines[indexOfLastVisibleLine];
        let indexOfLastWord = lastVisibleLine.length - 1;
        let lastVisibleLineWidth = lastVisibleLine.map(
          (element) => element.getBoundingClientRect().width
        ).reduce(
          (width, sum) => width + sum, 0
        );
        let ellipsisWidth = this.ellipsisElement.nativeElement.getBoundingClientRect().width;
        for (let i = indexOfLastWord; lastVisibleLineWidth + ellipsisWidth >= containerWidth; i--) {
          let wordWidth = lastVisibleLine[i].getBoundingClientRect().width;
          lastVisibleLineWidth -= wordWidth;
          this.words.splice(-1, 1);
        }


        this.showEllipsis = true;
      }
      this.cdRef.detectChanges();

      // delay is to prevent from font loading issues
    }, 1000);

  }

  deductLineHeight(lines: HTMLElement[][]): number {
    try {
      let rect0 = lines[0][0].getBoundingClientRect();
      let y0 = rect0['y'] || rect0['top'] || 0;
      let rect1 = lines[1][0].getBoundingClientRect();
      let y1 = rect1['y'] || rect1['top'] || 0;
      let lineHeight = y1 - y0;
      if (lineHeight > 0){
        return lineHeight;
      }
    } catch (e) {}

    return null;
  }

  getLines(nodes: HTMLElement[], clientWidth: number): HTMLElement[][] {
    let lines = [];
    let currentLine = [];
    let currentLineWidth = 0;

    nodes.forEach((node) => {
      if (!node.getBoundingClientRect){
        return;
      }

      let nodeWidth = node.getBoundingClientRect().width;
      if (currentLineWidth + nodeWidth > clientWidth){
        lines.push(currentLine);
        currentLine = [];
        currentLineWidth = 0;
      }
      currentLine.push(node);
      currentLineWidth += nodeWidth;
    });
    lines.push(currentLine);

    return lines;
  }

  endsWithHyphen(index: number): boolean {
    let length = this.words[index].length;
    return this.words[index][length - 1] === '-' && this.words[index + 1] && this.words[index + 1][0];
  }
}
Martin Cremer
źródło
0

Tutaj zrobiłem kolejną bibliotekę z szybszym algorytmem. Proszę sprawdzić:

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

Aby zainstalować za pomocą altany:

bower install fast-ellipsis

Aby zainstalować za pomocą npm:

npm install fast-ellipsis 

Mam nadzieję, że ci się podoba!

I. Ahmed
źródło
-2

Nie jesteś pewien, czy tego właśnie szukasz, używa minimalnej wysokości zamiast wysokości.

    <div id="content" style="min-height:10px;width:190px;background:lightblue;">
    <?php 
        function truncate($text,$numb) {
            // source: www.kigoobe.com, please keep this if you are using the function
            $text = html_entity_decode($text, ENT_QUOTES);
            if (strlen($text) > $numb) {
                $text = substr($text, 0, $numb);
                $etc = "..."; 
                $text = $text.$etc;
            } 
            $text = htmlentities($text, ENT_QUOTES);
            return $text;
        }
        echo truncate("this is a multi-lines text block, some lines inside the div, while some outside", 63);
    ?>
    </div>
khairil
źródło
4
Problemem jest liczba 63 w twoich kodach, jeśli numer jest znany, wszystko staje się łatwe, wystarczy obcięcie funkcji, jak twoje kody. Jak jednak poznać numer? Innymi słowy, skąd wiedzieć, gdzie tekst zostanie podzielony na linie? Jeśli można odpowiedzieć na to pytanie, to problem można po prostu rozwiązać w logice „1, oblicz liczbę; 2, obetnij”
Edward
-3

Wystarczy bardzo prosta funkcja.

Dyrektywa:

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

Widok:

<#p>{{truncateAlbumName(album.name)}}<#/p>
yamenator
źródło
3
Jak już omówiliśmy w innych odpowiedziach, problem w twoim kodzie polega na liczbie 36. Poza tym, że kod jest specyficzny dla określonej szerokości kontenera, jest również niedokładny: w przypadku czcionek o niezmienionej szerokości mogą występować duże różnice między literami . Zobacz iiiiiiiiiivs MMMMMMMMMM(chociaż obecna czcionka nie jest tak widoczna: D).
kapa