开发者

Can I wrap each line of multi-line text in a span?

I've been trying to figure out how to do this (if it's even possible) and have drawn a blank...

I have some text that will开发者_StackOverflow wrap onto multiple lines. I want to detect each individual line, and wrap it in a span. Finally, I want to assign a class to each span from a looping array.

For example...!

<div id="quote">
    I have some text that
    wraps onto three lines
    in this container
</div>

I want to get my jQuery to parse those lines, detect where it wraps, and turn it into this:

<div id="quote">
    <span class="red-bg">I have some text that</span>
    <span class="orange-bg">wraps onto three lines</span>
    <span class="yellow-bg">in this container</span>
</div>

The reason that I want to do this dynamically is that I'm doing it within responsive templates, so sometimes the same text will only wrap onto two lines, or maybe four in an iPhone.

Is this doable? I've found this -> http://vidasp.net/tinydemos/numberOfLines.html which calculates the number of lines used in a block of text, but that doesn't really extend to do what I need.


It seems like you're asking how to split the text where it is naturally wrapped by the browser. Unfortunately, this isn't straightforward at all. Neither is it robust — consider the following scenario:

  • User browses to your page, the div is rendered and the onload event fires,
  • 3 span elements are created from the text node, 1 for each wrapped line of text,
  • The user resizes the browser and the size of the div changes.

The result is that the spans no longer correlate to where the lines start and finish. Of course, this scenario is avoidable using fixed-width elements or you can rejig the whole thing when the browser resizes, but that's just an example of how it can break.

Still, it's not easy. A similar question has come up before (albeit, with a different goal) and two solutions appeared, which could both be of help here:

Solution 1: getClientRects()

Don't actually wrap the text in spans, but get the position and dimensions of each line of text using getClientRects(). Then, create the number of spans necessary and position/resize them behind each line of text.

Pros

  • Fast; getClientRects returns the position of each line
  • Simple; the code is more elegant than solution 2

Cons

  • Wrapped text must be contained by an inline element.
  • No styling will actually apply to the text (like font-weight or font-color). Only useful for things like background-color or border.

The demo provided with the answer shows how you can highlight the line of text currently beneath the mouse.

Solution 2: Split, join, loop, merge

Split the text into an array using the split() method with a word boundary or white-space as the argument passed. Rejoin the array into a string with </span><span> between each element and wrap the whole thing with <span> and </span>, and replace the original text node with the resulting HTML in the containing element. Now, iterate over each of those span elements checking its y position within the container. When the y position increases, you know that you've reached a new line and the previous elements can be merged into a single span.

Pros

  • Each line can be styled with any CSS property, like font-weight or text-decoration.
  • Each line can have its own event handlers.

Cons

  • Slow and unwieldy due to the numerous DOM and string operations

Conclusion

There may be other ways to achieve your goal, but I'm not sure of any myself. TextNode.splitText(n) can split a TextNode in twain (!) when passed a numeric index of the character you want to split on. Neither of the above solutions are perfect, and both break as soon as the containing element resizes.


I put together a fiddle implementing solution #2 by Andy E (above). I.e. Split, join, loop, merge

Here's the algorithm:

var spanInserted = $('#someText').html().split(" ").join(" </span><span>");
var wrapped = ("<span>").concat(spanInserted, "</span>");
$('#someText').html(wrapped);
var refPos = $('#someText span:first-child').position().top;
var newPos;
$('#someText span').each(function(index) {
    newPos = $(this).position().top   
    if (index == 0){
       return;
    }
    if (newPos == refPos){
        $(this).prepend($(this).prev().text() + " ");
        $(this).prev().remove();
    } 
    refPos = newPos;
});

Enjoy...


var classes = ",red-bg,orange-bg,yellow-bg".split(",")
var txt = $('#quote').html().split("\n")
//this gives you FIVE items because of the leading and trailing CRs
//so we skip the first and last item in the loop
var output = ""
for(var x=1;x<txt.length-1;x++) {
    output = output + "<span class='"+classes[x]+"'>"+txt[x]+"</span>"
}
$('#quote').html(output)


This will get you the text node but I'm not sure it helps

$("#quote")
  .contents()
  .filter(function() {
    return this.nodeType == 3;
  })
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜