Html injection change properties of element saved by instance
As soon as I try to insert the original HTML text into the body of the document, the saved instance of the element retrieved with .querySelector();
unexpectedly resets the .clientHeight and .clientWidth properties.
The next page shows the problem
<head>
<script>
window.addEventListener('load', pageInit);
function pageInit() {
// saving instance for later use
var element = document.querySelector('#element')
alert(element.clientHeight); // returns 40
document.querySelector('body').innerHTML += '<p>anything</p>';
alert(document.querySelector('#element').clientHeight); // returns 40
alert(element.clientHeight); // returns 0
}
</script>
<style>
#element {
height: 40px;
}
</style>
</head>
<body>
<div id="element"></div>
</body>
Why exactly are the properties of an instance of an atomic variable getting reset? As stated in this question the addEventListener went away after adding innerHTML the eventListeners are detached, but that still doesn't explain why this node element still exists and why its properties were reset from.
source to share
You're having an issue where the browser has remelted the document and reset its saved properties. Also known as Thrashing Layout . Accessing certain DOM properties (and / or calling certain DOM methods) will "force the browser to compute style and layout synchronously." This is a quote from Paul Irish for a complete list of what motivates layout / payment . Although innerHTML
not included there, pretty sure it's the culprit.
In your example, the first warning works for obvious reasons. The second works because you are asking the browser to go and find the element and get the property again. The third doesn't work because you are relying on the stored value (which is no longer stored).
The easiest way is to use requestAnimationFrame
when using methods / properties that will force payment.
window.addEventListener('load', pageInit);
function pageInit() {
// saving instance for later use
var element = document.querySelector('#element');
console.log("before:", element.clientHeight); // returns 40
requestAnimationFrame(function() {
document.querySelector('body').innerHTML += '<p>anything</p>';
});
console.log("WITH rAF after:", element.clientHeight); // returns ~0~ 40!
// out in the wild again
document.querySelector('body').innerHTML += '<p>anything</p>';
// oh no!
console.warn("W/O rAF after:", element.clientHeight); // returns 0
}
#element {
height: 40px;
}
<div id="element"></div>
source to share
It would seem that when you reset an innerHTML
element body
, you are causing the entire page to re-render. Thus div#element
, the one shown on the page is not the same element pointed to by the JavaScript variable element
; rather, the one you see is a new element that was created when the page was re-rendered. However div#element
, that element
indicates that it still exists; it has not yet been destroyed by the browser garbage collector.
To prove this, try replacing the last warning (the one that notifies 0) with console.log(element)
, and then try to click an item in the console window. You will see <div id="element"></div>
, but when you click on it, nothing happens since the div is not on the page.
What you want to do instead is the following:
const newEl = document.createElement('p');
newEl.innerHTML = 'anything';
document.querySelector('body').appendChild(newEl);
instead of setting a property body.innerHTML
.
FYI, you should probably switch alert
to console.log
s as notifications annoy people hell and the console is the way to test everything in development.
source to share