8

This is an example:

<div class="parent">
  Contrary to popular belief Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.
</div>

this div is 200px width, and I need to catch the last line I can see in the browser, wrapping it with a span. Such as <span>it over 2000 years old.</span> in my case.

Is this possible with jquery/javascript? Or at least get the lenght of this "last" line.

EDIT: I think I find a good way: https://jsfiddle.net/qqkxyq42/2/

markzzz
  • 47,390
  • 120
  • 299
  • 507
  • Are you saying that you know the substring before hand? If so split on the comma, right? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split. – Ronnie Royston May 09 '16 at 15:49

4 Answers4

4

This is probably a bit more hardcore than you were hoping, however you could check the height of the parent, and start removing word by word, checking it every iteration, until the height changes. Then you'll know at what word or symbol the last line begins.

var words = text.split(' '); // An array of all the words split by spaces, since that's where text breaks by default.
var lastLine = []; // Put all words that don't change the height here.
var currentHeight = parentEl.clientHeight; // The starting height.

while(words[0]){
  parentEl.innerText = words.join(' ');
  if (parentEl.clientHeight < currentHeight) {
    var span = document.createElement('span');
    span.innerText = lastLine.join(' ');
    parentEl.appendChild(span);
    break;
  }

  lastLine.shift(words.pop);
}

I'm assuming you're doing this to style the text in some way, currently there is a pseudo-selector called first-line, which targets the first line of text on the selector, however we've yet to see a last-line or nth-line(#) selectors, which is a shame, as it seems like a pretty reasonable thing to have.

GMchris
  • 5,439
  • 4
  • 22
  • 40
  • Good, but I think OP needs this for something weird that can achieve with something better than this. My upvote because you are answer exactly the question, but I don't like to use this kind of things in my projects. – Marcos Pérez Gude May 09 '16 at 16:22
3

This works. Goes word by word calculating the width

https://jsfiddle.net/stevenkaspar/ht7ostyx/9/

<div

 class="parent" id='text'>
  Contrary to popular belief Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.
</div>

<script>

function getWidth(pre_space, str, post_space, append_elem){
  var pre = (pre_space) ? '&nbsp;' : '';
  var post = (post_space) ? '&nbsp;' : '';

  var tmp_div = $('<span style="white-space: nowrap;">'+pre+str+post+'</span>');
  append_elem.append(tmp_div);
  var width =  tmp_div.width();
  tmp_div.remove();
  return width;
}
function linkEndLine(elem_id){
  var elem = document.getElementById(elem_id);
  var width = $(elem).width();
  var text = elem.innerText;
  var words = text.split(' ');
  var line_width = 0;
  var current_line = '';
  var lines = [];
  words.map(function(word, index){

    if(line_width == 0){
      line_width += getWidth(false, word, false, $(elem));
    }
    else {
      line_width += getWidth(true, word, false, $(elem));
    }

    if( (line_width / width) > 1){
      lines.push(current_line);

      line_width = getWidth(false, word, false, $(elem)); // new line
      current_line = '';
    }
    current_line += ((current_line != '') ? ' ' : '') + word;

    if(index == (words.length-1)){
      lines.push(current_line);
    }
  })
  var end_index = lines.length - 1;
  lines[end_index] = '<a href="#">'+lines[end_index]+'</a>';
  elem.innerHTML = lines.join(' ');
}
linkEndLine('text');

</script>
Steven Kaspar
  • 1,147
  • 10
  • 14
3

you can use document.caretPositionFromPoint for this, if you know or can approximate the line height of the text in the container. Here is an example fiddle: https://jsfiddle.net/xtcadpo0/

The function basically allows us to grab the offset position of text for any element at the given coordinates, and then we use simple string slicing on the textContent property of the element. We use this to grab the position of the point just before the text we need, and get the offset.

This solution is quite friendly to DOM performance as well, since we don't insert new elements or mutate the ones already existing.

Hope that helps.

Edit: just saw that you want to wrap the last line in a span, which you can do like this: https://jsfiddle.net/xtcadpo0/1/

It splits the text node and replaces the replacement with a wrapped element.

Here is the code:

function fn(el) {
  var range;
  var textNode;
  var offset;

  var e = {};

  var height = el.clientHeight;
  var lh = parseFloat(getComputedStyle(el).lineHeight, 10) || 18;
  var D = height / lh;
  var secondLastLineStart = (D - 1.5) * lh;
  var pos = el.getBoundingClientRect();
  e.x = pos.right;
  e.y = secondLastLineStart;

  if (document.caretPositionFromPoint) {
    range = document.caretPositionFromPoint(e.x, e.y);
    textNode = range.offsetNode;
    offset = range.offset;
  } else if (document.caretRangeFromPoint) {
    range = document.caretRangeFromPoint(e.x, e.y);
    textNode = range.startContainer;
    offset = range.startOffset;
  }
  var repl = textNode.splitText(offset);
  var newEl = document.createElement('span');
  textNode.parentNode.replaceChild(newEl, repl);
  newEl.appendChild(repl);
}

fn(document.getElementById('x'));
user3459110
  • 6,961
  • 3
  • 26
  • 34
  • Nice solution, you should embed the code in the answer rather than just on JSfiddle. – Loktar May 09 '16 at 17:13
  • This is a very cool solution. Can you embed it here in a StackSnippet (also can you add some comments as to what each of your lines are doing?) – Tuvia May 09 '16 at 17:13
0

For this purpose you must get all works in text using javascript split method. Then add every word to element and get element height. If element height reached to first height of element, that means this word is in last line. See my example

var words = $(".parent").text().split(" ");
var elementHeight = $(".parent").height();
var lastLine = ""; 

$(".parent").text("");
$.each(words, function(index, value){
    $(".parent").append(value + " ");   
    if ($(".parent").height() == elementHeight)
        lastLine += value + " ";
});

var html = $(".parent").html().replace(lastLine.trim(), "<span>" + lastLine + "</span>");
$(".parent").html(html);
.parent {
    width: 200px;
}

span {
    background: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="parent">
  Contrary to popular belief Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old.
</div>

If you want to do this work using pure javascript see jsfiddle

Mohammad
  • 21,175
  • 15
  • 55
  • 84