Search code examples
javascriptinnertext

How can I make `.innerText` ignore invisible children of an invisible element?


Result of the test code below:

div[0].innerText === "aaaaa zzzzz"
div[1].innerText === "␤aaaaa␤invisible␤zzzzz␤"

How can I force innerText to give the same result for div[1] as it gives for div[0]?

I’ve tried to append div[1] to a temporary document but, since the document wasn’t actually displayed, it didn’t help. Only appending it to a literally visible document works.

Test code

var div = [];
div[0] = document.getElementById("visible");
div[1] = div[0].cloneNode(true);

show(0);
show(1);

function show(i) {
    document.getElementById("output").innerHTML += 
      "<p>div[" + i + "].innerText === <code>" + 
      div[i].innerText.replace(/\n/g, "␤") + "</code></p>";
}
#visible {display: block; font-family: sans-serif; font-size: larger; color: red;}
code {background-color: lightgray; padding: 0 .318em;}
<div id="visible">
<span style="display: inline">aaaaa</span>
<span style="display: none">invisible</span>
<span style="display: inline">zzzzz</span>
</div>

<div id="output"></p>


Solution

  • Only appending it to a document literally visible to the user works.

    But the user doesn't necessarily have to see that. :-) If you append it, grab innerText, and then remove it, the user will never see it:

    var div = [];
    div[0] = document.getElementById("visible");
    div[1] = div[0].cloneNode(true);
    
    show(0);
    document.body.appendChild(div[1]);  // *****
    show(1);
    document.body.removeChild(div[1]);  // *****
    
    function show(i) {
        document.getElementById("output").innerHTML += 
          "<p>div[" + i + "].innerText === <code>" + 
          div[i].innerText.replace(/\n/g, "␤") + "</code></p>";
    }
    #visible {display: block; font-family: sans-serif; font-size: larger; color: red;}
    code {background-color: lightgray; padding: 0 .318em;}
    <div id="visible">
    <span style="display: inline">aaaaa</span>
    <span style="display: none">invisible</span>
    <span style="display: inline">zzzzz</span>
    </div>
    
    <div id="output"></p>

    Alternately, since the element isn't in the DOM, it can't be made invisible by CSS, only inline styles. I can't think of any other inline style that would make the text get left out of innerText other than your display: none and visibility: hidden (opacity: 0, for instance, doesn't do it), so it's trivial to exclude those and normalize whitespace for non-pre elements:

    function getInnerText(element) {
      var node, text = "";
      if (element.style.display.toLowerCase() !== "none" && element.style.visibility.toLowerCase() !== "hidden") {
        for (node = element.firstChild; node; node = node.nextSibling) {
          if (node.nodeType === 3) {
            text += node.nodeValue;
          } else if (node.nodeType === 1) {
            text += getInnerText(node);
          }
        }
      }
      // Normalize all whitespace if not "pre"
      if (element.tagName !== "PRE" && element.style.whiteSpace.toLowerCase().indexOf("pre") == -1) {
        text = text.replace(/\s+/g, ' ');
      }
      return text;
    }
    

    That may well need tweaking (I don't think it handles <div>stuff<pre>big gap</pre></div> properly), but you can run with the idea if you don't want to use the first solution above...

    Example:

    var div = [];
    div[0] = document.getElementById("visible");
    div[1] = div[0].cloneNode(true);
    
    show(0);
    document.body.appendChild(div[1]);  // *****
    show(1);
    document.body.removeChild(div[1]);  // *****
    
    function show(i) {
        document.getElementById("output").innerHTML += 
          "<p>div[" + i + "].innerText === <code>" + 
          getInnerText(div[i]).replace(/\n/g, "␤") + "</code></p>";
    }
    
    function getInnerText(element) {
      var node, text = "";
      if (element.style.display.toLowerCase() !== "none" && element.style.visibility.toLowerCase() !== "hidden") {
        for (node = element.firstChild; node; node = node.nextSibling) {
          if (node.nodeType === 3) {
            text += node.nodeValue;
          } else if (node.nodeType === 1) {
            text += getInnerText(node);
          }
        }
      }
      // Normalize all whitespace if not "pre"
      if (element.tagName !== "PRE" && element.style.whiteSpace.toLowerCase().indexOf("pre") == -1) {
        text = text.replace(/\s+/g, " ");
      }
      return text;
    }
    #visible {display: block; font-family: sans-serif; font-size: larger; color: red;}
    code {background-color: lightgray; padding: 0 .318em;}
    <div id="visible">
    <span style="display: inline">aaaaa</span>
    <span style="display: none">invisible</span>
    <span style="display: inline">zzzzz</span>
    </div>
    
    <div id="output"></p>