2

(NOTE: This question isn't the same as the similar one above, as it is about differences between attached and detached DOM trees.)

A simple bit of HTML containing a DIV with no whitespace between its elements:

<!DOCTYPE html>
<html>

  <body>
    <div><h1>The Title</h1><p>A paragraph.</p><p>A second paragraph.</p></div>
  </body>

  <script type="text/javascript">

   const div = document.querySelector("div");

   console.log(div.innerText);

   const clone = div.cloneNode(true);
   console.log(clone.innerText);

   document.body.appendChild(clone);
   console.log(clone.innerText);

  </script>
</html>

I output innerText to the console three times.

The first time is that of the original DIV:

The Title

A paragraph.

A second paragraph.

The second is that of the cloned DIV, which I would expect to be the same, but is:

The TitleA paragraph.A second paragraph.​

The third is again that of the cloned DIV, but after it has been added to the document, now what I would expect it to be:

The Title

A paragraph.

A second paragraph.

Why is the spacing different when it is not part of the document?

IpsRich
  • 797
  • 10
  • 23

1 Answers1

5

This is a quirk of innerText for detached DOM nodes:

If the element itself is not being rendered (for example, is detached from the document or is hidden from view), the returned value is the same as the Node.textContent property.

This is because innerText takes CSS into account (in this case, the display: block properties of the tags to insert new lines (\n)).

From the algorithm for computing innerText, step 9 says:

If node's used value of 'display' is block-level or 'table-caption', then append 1 (a required line break count) at the beginning and end of items.

As you see, after you insert the cloned node into the DOM, then innerText returns what the original node did because it is able to compute those CSS properties:

const div = document.querySelector("div");

console.log("Original, innerText:", JSON.stringify(div.innerText));
console.log("Original, textContent:", JSON.stringify(div.textContent));

const clone = div.cloneNode(true);
console.log("Detached clone, innerText:", JSON.stringify(clone.innerText));
console.log("Detached clone, textContent:", JSON.stringify(clone.textContent));

document.body.appendChild(clone);

console.log("Attached clone, innerText:", JSON.stringify(clone.innerText));
console.log("Attached clone, textContent:", JSON.stringify(clone.textContent));
<div><h1>The Title</h1><p>A paragraph.</p><p>A second paragraph.</p></div>
romellem
  • 5,792
  • 1
  • 32
  • 64
  • Thanks for the explanation, that makes a lot of sense. I now need to investigate another issue I'm having, where some of the cloned node's children have non-empty `innerHTML` and `textContent` properties, but empty `innerText` properties! I thought this must be a bug in my Firefox, but Chrome is doing it too! Probably one for a separate question. – IpsRich Oct 04 '22 at 08:02
  • 1
    UPDATE: I eventually realised that this was another quirk of `innerText` where some of the elements had opacity of 0 and so their innerText was empty but the `textContent` wasn't! – IpsRich Oct 04 '22 at 08:20