5

I have spent two days on this, so I will be disheartened if there is a simple answer. I am trying to put a span tag around every letter in a div, while leaving the rest of the tags intact.

<div id="div">
    <p>
      Of course some of the <strong>text is in other tags</strong> and <strong>some 
      is  in <em>nested tags</em>, etc.</strong>
    </p>
</div>

I get very close, but something always trips me up in the end.

Rob
  • 61
  • 1
  • 3
  • Are the letters in the child tags also supposed to get a surrounding tag? – Pekka Jun 04 '10 at 20:15
  • This is not aware of nested tags but may give some groudnwork: http://stackoverflow.com/questions/1966476/javascript-process-each-letter-of-text – Pekka Jun 04 '10 at 20:16
  • This may come close: http://stackoverflow.com/questions/2530238/how-to-target-specific-letter-word-with-jquery – Pekka Jun 04 '10 at 20:17
  • Interesting question in concept, but I'm scared to see how bloated your markup will become in practice.. – KP. Jun 04 '10 at 20:24
  • There is no simple answer because this idea is insane. – ghoppe Jun 04 '10 at 21:18
  • I think it is the best solution to my problem. I don't think the overhead will be too bad. I have been able to get it working enough to make some initial tests and it takes fractions of a millisecond to convert fairly large amounts of text. The probelm is in keeping the exosting tags in place while wrapping each letter. – Rob Jun 05 '10 at 02:37

3 Answers3

2

I got it! This may not be the optimal solution, but it works! Also note that because of the extra tags, whitespace may get messed up. This also wraps tabs but that's easy to fix too.

function wrap(target) {
    var newtarget = $("<div></div>");
    nodes = target.contents().clone(); // the clone is critical!
    nodes.each(function() {
        if (this.nodeType == 3) { // text
            var newhtml = "";
            var text = this.wholeText; // maybe "textContent" is better?
            for (var i=0; i < text.length; i++) {
                if (text[i] == ' ') newhtml += " ";
                else newhtml += "<span>" + text[i] + "</span>";
            }
            newtarget.append($(newhtml));
        }
        else { // recursion FTW!
            $(this).html(wrap($(this)));
            newtarget.append($(this));
        }
    });
    return newtarget.html();
}

Usage:

$("#div").html(wrap($("#div")));
Tesserex
  • 17,166
  • 5
  • 66
  • 106
  • This doesn't seem to work for me - I can see that it correctly builds up the newhtml string in a debugger, but `$(this).html(newhtml)` doesn't result in any change to the output... – Ryley Jun 04 '10 at 21:04
  • I fixed it! Maybe more people can find ways to optimize? – Tesserex Jun 04 '10 at 21:23
  • I also just tested it on a saved copy of the jQuery api page. It took a second, but it worked, the page appearance didn't change, everything is intact. Seems to work. – Tesserex Jun 04 '10 at 21:30
  • I think that wholeText has some compatibility issues, but I might be able to use this as a springboard. I think the recursion will help me. I was trying to stay away from textNode==3, but I suppose it is pretty safe on all browsers. I will work on it tomorrow. I also need the whitespace wrapped, but that is easy enough. – Rob Jun 05 '10 at 02:33
  • Just a note - this script will fail in IE8 - throwing error `Unable to get value of the property 'length': object is null or undefined `. – easwee Feb 12 '13 at 14:36
1
function init(target) {
var newtarget = $('<div></div>'); 
nodes = target.contents().clone(); // the clone is critical! 
nodes.each(function(i,v) { 
    if (v.nodeType == 3) { // text
        if($.trim($(this).text()).length>0){
            var letters=$(this).text().split('');
            for (var j = 0; j < letters.length; j++) {
                newtarget.append('<span class="letter">'+letters[j]+'</span>')
            }
        }
    } 
    else { // recursion FTW! 
        newtarget.append($(this)); 
        $(this).html(init($(this))); 
    } 
});
return newtarget.html(); 
} 

This works fairly well. However, ie (7 anyway), strips out all of the spaces. Also, should I remove newtarget from the dom at the end of the function? And what about the clone? Should that be removed?

easwee
  • 15,757
  • 24
  • 60
  • 83
Rob
  • 61
  • 1
  • 3
1

You might want to have a look at my plugin, TextGrad, which allows to do exactly that with the "spanize" method added in jQuery object.

http://github.com/subtenante/TextGrad

Edit:

I forgot (!) I have a demo there :

http://www.si-les-idees-suffisaient.net/jquery/textgrad.html

glmxndr
  • 45,516
  • 29
  • 93
  • 118