0

Look at this <div>-Element:

<div id="text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque pena&shy;tibus et magnis dis parturient montes, nascetur ridiculus mus.</div>

and this css:

div#text {
    font-family: Arial;
    font-size: 16px;
    width: 200px;
}

When this div is rendered, it is split into many lines and might look like this block:

Lorem ipsum dolor sit amet,
consectetuer adipiscing elit.
Aenean commodo ligula
eget dolor. Aenean massa.
Cum sociis natoque pena-
tibus et magnis dis
parturient montes, nascetur
ridiculus mus.

Is there a way to extract, let's say, the 6th line of this div's content using Javascript? This means, I want a function, to which I pass the value 3 and then it returns »tibus et magnis dis«. Is this possible? If so, how can this be done?

Whatch the soft hyphen (&shy;) in the word »penatibus« that makes the word be splittet into two parts.

In a similar way, think of the css-property hyphen:

div#text {
    hyphens: auto;
}

This causes the rendering-machine to hyphenate the text without the need of inserting dashes or &shy;-entities.

Remember, that there is no newline or <br> or any other marker in the text that splits the text into lines. It is just the rendering-machines word-wrap-algorithm together with css-styles and the textstring itself that is responsible for which words are written into which line.

This is also the reason why this question is different form this.

This Question is also different from this one because the solutions posted there ignore the influence of hyphenations.

Also think of ligatures (like f followed by i in many fonts is replaced by a new glyph, fi, that is a little bit shorter than a normal f followed by a normal i would be. This may influence word-wrap and hyphenation. But when you put each character of your text into a <span>-element to be able to read the position of all these elements, then you will not see f and i being replaced by its shorter ligature fi, and so this method does not return correct results in all cases.

Community
  • 1
  • 1
Hubert Schölnast
  • 8,341
  • 9
  • 39
  • 76
  • 1
    I am almost 100% certain this is not going to be possible. What are you trying to achieve? – Charlie Wynn May 04 '15 at 19:00
  • @MelanciaUK: My question is similar to the one you linked to, but there the influence of hyphens is ignored in all answeres. I edited my question to make clear, that this is also an important fact that needs to be dealt with in an answer. – Hubert Schölnast May 04 '15 at 19:20
  • @CharlieWynn: I need to know where exactly the rendering machine makes the line-breaakes, because I encountered a case where the browser rendered a newly inserted text into 3 lines, and some miliseconds later it changed it into 2 lines. To bad that my script measured the hight of that div just inbetween, and so got wrong results. Look here fore details: http://stackoverflow.com/questions/30016844/javascript-too-fast-for-dom-manipulation – Hubert Schölnast May 04 '15 at 19:24
  • Can you use one of the answers in the other post and add a check to see if the last character in the line is a hyphen? If it is, simply grab all characters before the next blank space. – SRing May 04 '15 at 19:24
  • You say the problem is created because your script performs an action before a browser optimization is complete... how about just having your script run with a short delay - `setTimeout(function() {[your code here]}, 100)` (or however long it needs...) - and see if you can simply avoid the problem altogether? – Guy Passy May 04 '15 at 20:09
  • @GuyPassy: Maybe you should write this answer to the question to which it belongs, which is not this question here. – Hubert Schölnast May 04 '15 at 20:38
  • @HubertSchölnast Dude... you wrote "The problem is, that the browser (Safari or Chrome on OS X) render a certain newly inserted text into 3 Lines (giving a hight of 60px), and some miliseconds later improved rendering [...]". I'm suggesting you wait for the render to complete. – Guy Passy May 04 '15 at 20:47
  • @GuyPassy: Yes, it is true that I wrote this. But I also wrote, that this page: http://stackoverflow.com/questions/30016844/javascript-too-fast-for-dom-manipulation contains the question that deals with that milisecond-rerendering-problem. Here we talk about extracting lines from a div. Your answer does not address the line-extracting problem, so it makes no sense to post it here. Your answer addresses the milisecond-rerendering-problem, so you should post it there: http://stackoverflow.com/questions/30016844/javascript-too-fast-for-dom-manipulation – Hubert Schölnast May 04 '15 at 20:54
  • 1
    @HubertSchölnast *sheepishly* oh I see – Guy Passy May 04 '15 at 20:59
  • 1
    I don't see how this got closed as a duplicate for the mentioned post. It simply isn't or did those floggers never read through both questions? – Icepickle May 05 '15 at 06:10
  • @Icepickle: I nominated the Question for reopening. Please klick on "reopen" just below the ende of my question. – Hubert Schölnast May 05 '15 at 10:23
  • @HubertSchölnast alas, I need 3k to make this happen ;) – Icepickle May 05 '15 at 18:12

1 Answers1

2

I guess you could trick a bit, and copy the width of the existing element, and then add word per word to see if the height of the new element changes, then increase the line.

I wrote a basic version of this. This only takes into account the width of the div element, none of the other styles (like font-size, letter-spacing, ... for that, you could copy the css styles from the element you wish to get the line nr from)

function copyStyle(source, target) {
  var computedStyle = window.getComputedStyle(source, null),
    prop, obj = computedStyle, i, len, style = '';
  for (i = 0, len = computedStyle.length; i < len; i++) {
      prop = computedStyle[i];
      style += prop + ': ' + obj[prop] + ';';
  }
  target.style = style;
}

function extractHyphens(array) {
  var word, i, len, shyElement = document.createElement('span');
  shyElement.innerHTML = '&shy;';
  for (i = 0, len = array.length; i < len; i++) {
    word = array[i];
    if (word.indexOf(shyElement.innerHTML) >= 0) {
      word = word.split(shyElement.innerHTML);
      array.slice(i, 1, word);
    }
  }
}

function getLine(element, lineNr, callback, measuredWidth) {
  var hiddenElement,
    sourceElement = document.getElementById(element),
      textarea = document.getElementById('textarea'),
    lastHeight,
    outline = '',
    curline = -1,
    words, word;

  if (typeof measuredWidth === 'undefined' ||
    measuredWidth !== sourceElement.offsetWidth) {
    measuredWidth = sourceElement.offsetWidth;
    setTimeout(function() {
      getLine(element, lineNr, callback, measuredWidth);
    }, 100);
  } else {
    hiddenElement = document.createElement('div');
    words = sourceElement.innerHTML;
    words = words.split(' ');
    extractHyphens(words);
    copyStyle(sourceElement, hiddenElement);
    hiddenElement.style.visibility = 'hidden';
    // we don't want anything affecting the height of the "hidden" element
    hiddenElement.style.height = 'auto';
    delete hiddenElement.style['minHeight'];
    delete hiddenElement.style['maxHeight'];
    document.body.appendChild(hiddenElement);
    lastHeight = hiddenElement.offsetHeight;
    for (var i = 0, len = words.length; i < len; i++) {
      word = words[i];
      hiddenElement.innerHTML += word;
      if (lastHeight !== hiddenElement.offsetHeight) {
        curline++;
      }
      if (lineNr === curline) {
        outline += word + ' ';
      }
      hiddenElement.innerHTML += ' ';
      lastHeight = hiddenElement.offsetHeight;
      if (curline > lineNr) {
        break;
      }
    }
    document.body.removeChild(hiddenElement);
    setTimeout(function() {
      callback(element, outline);
    }, 0);
  }
}

function showResult(element, result) {
  alert(element + ': ' + result);
}

window.onload = function() {
  getLine('test', 1, showResult);
  getLine('test2', 6, showResult);
};
#test2 {
  width: 50px;
  font-size: 7px;
  padding: 3px;
}
div {
  padding: 5px;
}
<div id="test">lorem ipsum dolor sit amet, consectetuer adipiscing elit, aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque&shy;&raquo;penatibus&laquo; et magnis dis parturient montes, ascetur ridiculus mus.</div>
<div id="test2">lorem ipsum dolor sit amet, consectetuer adipiscing elit, aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, ascetur ridiculus mus.</div>
Icepickle
  • 12,689
  • 3
  • 34
  • 48
  • good idea, but too slow. The problem is, that the browser (Safari or Chrome on OS X) render a certain newly inserted text into 3 Lines (giving a hight of 60px), and some miliseconds later improved rendering, and suddenly the same text fitted into 2 lines (40px high). The Problem is, that my script measures the elements hight before the final rendering-optimozation-step, and works with the hight of 60px that the user never sees, because it will be changed so quickly to 40px. AND: How does your solution deal with soft hyphens (`­`)? – Hubert Schölnast May 04 '15 at 19:29
  • @HubertSchölnast: Could you then give a more appropriate test that could check why the current exercise is to slow? I can give an attempted solution for the resizing of the element, that would then restart once the height would be stable, however, I do not have OS X so it's hard to test for me). I updated the script a little to use a callback when the result was found (or not found), i tried around a bit with the ­ but since it's a rendered text it's not easy to get it separated (and in the end, doesn't affect the rendering how i thought it would). – Icepickle May 04 '15 at 22:02