Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance is really bad at ng-repeat. #9

Open
martingg88 opened this issue Sep 11, 2014 · 13 comments
Open

Performance is really bad at ng-repeat. #9

martingg88 opened this issue Sep 11, 2014 · 13 comments

Comments

@martingg88
Copy link

There is the worst performance if I implement it in ng-repeat..
Any help would be more appreciated.

@HuangJian
Copy link

Hi martingg88

It could be a bit later... But you can try my improvement if you still need it.
On my page, there are 10 very looooooooooong text elements in ng-repeat with data-ellipsis.
Running with the current code, re-sizing page takes 3~10 seconds to respond;
running with my improved code, it's real-time.

function buildEllipsis() {
  if (typeof(scope.ngBind) !== 'undefined') {
    var bindArray = scope.ngBind.split(" "),
      ellipsisSymbol = (typeof(attributes.ellipsisSymbol) !== 'undefined') ? attributes.ellipsisSymbol : '…',
      appendString = (typeof(scope.ellipsisAppend) !== 'undefined' && scope.ellipsisAppend !== '') ? ellipsisSymbol + '<span>' + scope.ellipsisAppend + '</span>' : ellipsisSymbol;

    attributes.isTruncated = false;
    element.html(scope.ngBind);

    // refine the algorithm to improve the performance significantly
    //                            [email protected] 20150228
    var desiredHeight = element[0].clientHeight;
    var actualHeight = element[0].scrollHeight;
    if (actualHeight > desiredHeight) {
      // PERFORMANCE IMPROVEMENT: calc the proper size by heights
      var size = Math.floor(bindArray.length *
                           desiredHeight / actualHeight);
      var text = bindArray.slice(0, size).join(' ');
      element.html(text + appendString);
      while (isOverflowed(element) && size > 0) {
        --size;
        // PERFORMANCE IMPROVEMENT: use String.substr rather than Array.join
        text = text.substr(0, text.length - bindArray[size].length - 1);
        element.html(text + appendString);
      }
    }
  }
}

@MarcoDeJong
Copy link

Tested in production environment. Huge improvement. Thank you!

@HuangJian
Copy link

@miraclemarc, please try this updated version. It places the ellipsis more accurate.

function buildEllipsis() {
  if (typeof(scope.ngBind) !== 'undefined') {
    var str = scope.ngBind,
      ellipsisSymbol = (typeof(attributes.ellipsisSymbol) !== 'undefined') ? attributes.ellipsisSymbol : '&hellip;',
      appendString = (typeof(scope.ellipsisAppend) !== 'undefined' && scope.ellipsisAppend !== '') ? ellipsisSymbol + '<span>' + scope.ellipsisAppend + '</span>' : ellipsisSymbol;

    attributes.isTruncated = false;
    element.html(scope.ngBind);

    var desiredHeight = element[0].clientHeight;
    var actualHeight = element[0].scrollHeight;
    if (actualHeight > desiredHeight) {
      attributes.isTruncated = true;

      var spliter = ' ';
      var lineHeight = parseFloat(element.css('line-height'));

      // the max possible characters that might not overflow the desired height
      var max = Math.ceil(str.length * (desiredHeight + lineHeight) / actualHeight);

      // the min characters that must not overflow the desired height
      var min = Math.floor(str.length * (desiredHeight - lineHeight) / actualHeight);
      min = str.substr(0, min).lastIndexOf(spliter);

      // set with the max possible size, then reduce its size word by word
      var size = str.indexOf(spliter, max);
      if (size < 0) {
        // no spliter after max
        size = max;
      }

      var text = str.substr(0, size).trim();
      var arr = str.substr(min, size - min).trim().split(spliter);
      var idx = arr.length;
      element.html(text + appendString);
      while (isOverflowed(element) && idx >= 0) {
        --idx;
        text = text.substr(0, text.length - arr[idx].length - 1);
        element.html(text + appendString);
      }
    }
  }
}

@MarcoDeJong
Copy link

Thank you! I will test your latest additions

Update:
Tested last snippet. But your first one actually works better for me.

@a-c-m
Copy link

a-c-m commented Mar 20, 2015

is this improvement going to get committed ?

@HuangJian
Copy link

@miraclemarc

yep, the first version is better in performance, but it could place the ellipsis incorrectly.
Like:

bla bla bla... (looooooooooong space in the last line)

or even

bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla...
(empty area could fill one or more lines)
(empty area could fill one or more lines)

@tangentlin
Copy link

Just curious instead of substracting words from the end, which could seriously hurt performance when the text is long, how about we use an idea similar to "binary search"? The key here is to find the "boundary position" where the text just overflows.

@tangentlin
Copy link

Here is a faster version based on the first version by @HuangJian , it uses binary search to find the "boundary position", along with other performance enhancement such as using .innerHTML instead $.html(), the improvement is more noticeable.

var domElement = element[0];

function getEmptySpaceLocations(text) {
    var spaceIndices = [];
    var re = /\s+/gm;
    var match;
    while ((match = re.exec(text)) != null) {
        spaceIndices.push(match.index);
    }
    return spaceIndices;
}
function getSubText(fullText, spaceIndices, index) {
    return fullText.substr(0, spaceIndices[index]);
}
function isSubTextOverflow(fullText, spaceIndices, index, appendString) {
    var text = getSubText(fullText, spaceIndices, index);
    domElement.innerHTML = text + appendString;
    return isOverflown(element);
}
function buildEllipsis() {
    if (typeof (scope.ngBind) !== 'undefined') {
        var text = scope.ngBind;
        var spaceIndices = getEmptySpaceLocations(text);
        attributes.isTruncated = false;
        element.html(text);
        var desiredHeight = element[0].clientHeight;
        var actualHeight = element[0].scrollHeight;
        if (actualHeight > desiredHeight) {
            var totalSpaceLocations = spaceIndices.length;
            var begin = 0;
            var end = totalSpaceLocations - 1;
            var lastOverflown = true;
            var currentIndex;
            var currentOverflown = true;
            var notFound = true;
            var seekedTimes = 0;
            var ellipsisSymbol = (typeof (attributes.ellipsisSymbol) !== 'undefined') ? attributes.ellipsisSymbol : '&hellip;';
            var appendString = (typeof (scope.ellipsisAppend) !== 'undefined' && scope.ellipsisAppend !== '') ? ellipsisSymbol + '<span>' + scope.ellipsisAppend + '</span>' : ellipsisSymbol;
            while (notFound) {
                currentIndex = begin + ((end - begin) >> 1);
                currentOverflown = isSubTextOverflow(text, spaceIndices, currentIndex, appendString);
                seekedTimes++;
                if ((currentOverflown != lastOverflown) && (end - begin) == 1) {
                    notFound = false;
                }
                else {
                    if (currentOverflown) {
                        end = currentIndex;
                    }
                    else {
                        begin = currentIndex;
                    }
                }
            }
            var truncatedText = getSubText(text, spaceIndices, currentIndex) + appendString;
            element.html(truncatedText);
            attributes.isTruncated = true;
            console.log('Seeked: ' + seekedTimes + ' Spaces: ' + totalSpaceLocations + ' Length: ' + text.length);
        }
    }
}
function isOverflown(thisElement) {
    return thisElement[0].scrollHeight > thisElement[0].clientHeight;
}

julpar pushed a commit to educarlabs/angular-ellipsis that referenced this issue Nov 20, 2015
@NinoFloris
Copy link

@ MauMaGau Bump, could you please integrate this fix like they did in the educarlabs fork (which is less maintained). This fix is a huge performance upgrade when the text that has to be ellipsed is very long.

@NinoFloris
Copy link

Ok so I have put my money where my mouth is a few weeks after I asked for this here and integrated it myself. However I still haven't opened a PR, something that slipped... because life.

Do know I have a present for you all 🎁 and please do nag me about it if I still haven't done this a while after this comment ^_^

@MarcoDeJong
Copy link

nag 💅

@eddieedease
Copy link

nag 💅

@NinoFloris
Copy link

There you go! #79

Teless added a commit to TranslucentComputing/angular-ellipsis that referenced this issue Jan 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants