MutationObserver
is a lesser known JavaScript feature which allows you to detect when elements in a web page are inserted, changed or removed. It is still relatively new, but it is supported by every modern browser.The web is full of demos and tutorials of MutationObserver, but it’s pretty hard to find examples of it actually being used in practice. Even a search of Github is almost all libraries and test cases. We’ve had a couple occasions to use it at Eager however, which I now have the opportunity to share.
Client-side Image Optimization
Believe it or not, it’s actually possible to swap the src’s of img tags before the browser begins to load them. We can use that to optimize our images without changing the HTML source of our page. This code uses a FireSize service to handle the actual optimization.
We start by setting up a
MutationObserver
which will call ourcheckNode
function with any new nodes which are added to the DOM:
Code language: JavaScript
var observer = new MutationObserver(function(mutations){
for (var i=0; i < mutations.length; i++){
for (var j=0; j < mutations[i].addedNodes.length; j++){
checkNode(mutations[i].addedNodes[j]);
}
}
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
If we run this code early in the head of the page, it will call our
checkNode
function with each DOM node as the browser parses the page’s HTML. This gives us the ability to check or mutate these nodes before they’ve ever been rendered.We can define our
checkNode
function to decide if this is an image for us to optimize.
Code language: JavaScript
checkNode = function(addedNode) {
if (addedNode.nodeType === 1 && addedNode.tagName === 'IMG'){
addedNode.src = optimizeSrc(addedNode.src)
}
}
Finally, we can define
optimizeSrc
to switch out our image’s src for an optimized one:
Code language: JavaScript
optimizeSrc = function(src) {
return "//firesize.com/" + src;
}
For a complete implementation, take a look at our FireSize app source code.
Initializing When An Element Becomes Available on the Page
It’s a common pattern to wait for
jQuery.ready
orDOMContentLoaded
to initialize code which depends on elements on the page. Those events don’t fire until the entire DOM has loaded however, meaning the page will start to be rendered before you have a chance to change or add to its content.Our pattern from the image optimization solution also works for detecting when any element becomes available, allowing you to initialize code which depends on that element at the exact first moment it’s possible. We can redefine
checkNode
to instead check if our element matches an arbitrary selector:
Code language: JavaScript
checkNode = function(addedNode) {
if (addedNode.nodeType === 1){
if (addedNode.matches('.should-underline')){
SmartUnderline.init(addedNode);
}
}
}