john c. j. john c. j. - 6 months ago 10
Javascript Question

Add CSS styles for non-tagged text

Is there a way to add CSS styles for text without tags? Maybe it could be done just with CSS, or maybe I need to use JS?

<html>
<body>

<p>Foo</p>

Bar

Bar

Bar

<p>Foo</p>

<div>

<p>Foo</p>

Bar

Bar

Bar

<p>Foo</p>

</div>

</body>
</html>


In other words, I want to make all "Bar"s red color. Is there a way to do it?




EDIT

My question was ambiguously formulated. T.J. Crowder answered very good, but I must note that the actual task was to match arbitrary text. So, in the example below, it should be matched all text about animals:

<html>
<body>

<p>Foo</p>

Cat

Cat and elephant are going through the jungle

Elephant

<p>Foo</p>

<div>

<p>Foo</p>

Green parrot

Red parrot

Yellow parrot

<p>Foo</p>

</div>

</body>
</html>

Answer

Your example is a bit ambiguous, because the Bar instances are the only things that are directly contained by body. If I assume you may have other things than Bar in that section, no, you can't apply CSS just to the Bar parts and not to the other parts.

In JavaScript, you could wrap the Bar text in an element (say, a span) and then style that element. The simple naive way is to use replace:

// Simple, but naive, and probably not what you want
document.body.innerHTML = document.body.innerHTML.replace(/\bBar\b/g, '<span class="wrapper">Bar</span>');

Here's an example, but keep reading:

document.body.innerHTML = document.body.innerHTML.replace(/\bBar\b/g, '<span class="wrapper">Bar</span>');
.wrapper {
  font-weight: bold;
  color: green;
}
<p>Foo</p>

Bar

Glarb

Bar

Glarb

Bar

<p>Foo</p>

<div>

  <p>Foo</p>

  Bar
  
  Glarb

  Bar
  
  Glarb

  Bar

  <p>Foo</p>

</div>

The problems with doing that are:

  • Bar will be matched everywhere, even in attributes and such
  • It will blow away the page entirely, and replace all of the elements on the page with new elements. That will disconnect event handlers, for instance.

So instead, you can do something more refined, find the text within text nodes and only wrap it when you find it, which A) Doesn't find it in the wrong place, and B) Doesn't affect event handlers on existing elements.

My answer here demonstrates how to wrap a portion of the text in a text node in another element. Here it is applied to your specific situation:

walk(document.body, "Bar");

function walk(node, targetString) {
  var child;

  switch (node.nodeType) {
    case 1: // Element
      for (child = node.firstChild; child; child = child.nextSibling) {
        walk(child, targetString);
      }
      break;

    case 3: // Text node
      handleText(node, targetString);
      break;
  }
}

function handleText(node, targetString) {
  var start, targetNode, followingNode, wrapper;

  // Does the text contain our target string?
  // (This would be a regex test in your http://... case)
  start = node.nodeValue.indexOf(targetString);
  if (start >= 0) {
    // Split at the beginning of the match
    targetNode = node.splitText(start);

    // Split at the end of the match
    followingNode = targetNode.splitText(targetString.length);

    // Wrap the target in an element; in this case, we'll
    // use a `span` with a class, but you'd use an `a`.
    // First we create the wrapper and insert it in front
    // of the target text.
    wrapper = document.createElement('span');
    wrapper.className = "wrapper";
    targetNode.parentNode.insertBefore(wrapper, targetNode);

    // Now we move the target text inside it
    wrapper.appendChild(targetNode);

    // Clean up any empty nodes (in case the target text
    // was at the beginning or end of a text ndoe)
    if (node.nodeValue.length == 0) {
      node.parentNode.removeChild(node);
    }
    if (followingNode.nodeValue.length == 0) {
      followingNode.parentNode.removeChild(followingNode);
    }
  }
}
.wrapper {
  font-weight: bold;
  color: green;
}
<p>Foo</p>

Bar

Glarb

Bar

Glarb

Bar

<p>Foo</p>

<div>

  <p>Foo</p>

  Bar
  
  Glarb

  Bar
  
  Glarb

  Bar

  <p>Foo</p>

</div>