CSS’s :has() can obviate JS
A MutationObserver interface picks up on changes to the DOM. But determining whether those changes include the addition of elements of interest takes some digging.
The MutationObserver constructor’s callback function receives an array of MutationRecords. Each MutationRecord has an addedNodes property, a NodeList of the nodes added to the view in the mutation which triggered the callback. Inspect these added nodes to see if any are ones you need to act on.
Say you want to add the attribute data-initialized to all elements with the class example. Start with a MutationObserver constructor that watches everything on the page (target of document.body, subtree: true) for the addition and removal of child nodes (childList: true)
jsconst observer = new MutationObserver()observer.observe(document.body, {childList: true,subtree: true,});
jsconst observer = new MutationObserver()observer.observe(document.body, {childList: true,subtree: true,});
Then, in the constructor’s callback function, find the nodes of interest and act on them
jsconst observer = new MutationObserver((mutationRecords) => {const els = mutationRecords// all added nodes.flatMap((mutationRecord) => {return Array.from(mutationRecord.addedNodes);})// just the ones of interest.filter((addedNode) => {return addedNode.classList.contains("example");});for (const el of els) {el.setAttribute("data-initialized", "");}});observer.observe(document.body, {childList: true,subtree: true,});
jsconst observer = new MutationObserver((mutationRecords) => {const els = mutationRecords// all added nodes.flatMap((mutationRecord) => {return Array.from(mutationRecord.addedNodes);})// just the ones of interest.filter((addedNode) => {return addedNode.classList.contains("example");});for (const el of els) {el.setAttribute("data-initialized", "");}});observer.observe(document.body, {childList: true,subtree: true,});
Other patterns
Where I use the name “mutationRecords” for the array of MutationRecords, some people use “mutationList”.
If this script comes after markup, take into account elements already on the page
jsfunction initialize(els) {for (const el of els) {el.setAttribute("data-initialized", "");}}const observer = new MutationObserver((mutationRecords) => {const els = mutationRecords// all added nodes.flatMap((mutationRecord) => {return Array.from(mutationRecord.addedNodes);})// just the ones of interest.filter((addedNode) => {return addedNode.classList.contains("example");});initialize(els);});observer.observe(document.body, {childList: true,subtree: true,});initialize(document.body.querySelectorAll(".example"));
jsfunction initialize(els) {for (const el of els) {el.setAttribute("data-initialized", "");}}const observer = new MutationObserver((mutationRecords) => {const els = mutationRecords// all added nodes.flatMap((mutationRecord) => {return Array.from(mutationRecord.addedNodes);})// just the ones of interest.filter((addedNode) => {return addedNode.classList.contains("example");});initialize(els);});observer.observe(document.body, {childList: true,subtree: true,});initialize(document.body.querySelectorAll(".example"));
Here’s a CodePen for that
jsfunction initializeAndObserve() {const root = // … e.g. `const root = document.body`function initialize(els) {for (const el of els) {// … e.g. `el.setAttribute("data-initialized", true)`}}const observer = new MutationObserver((mutationRecords) => {const els = mutationRecords// all added nodes.flatMap((mutationRecord) => {return Array.from(mutationRecord.addedNodes)})// just the ones of interest.filter((addedNode) => {return // … e.g. `return addedNode.hasAttribute("data-my-attribute")`});// initialize elements of interest that were added to the pageinitialize(els);})// observe changes to `root` and to its childrenobserver.observe(root, {childList: true,subtree: true,});// initialize elements of interest available at page loadinitialize(root./* … e.g. `root.querySelectorAll("[data-my-attribute]")` */);}initializeAndObserve();
jsfunction initializeAndObserve() {const root = // … e.g. `const root = document.body`function initialize(els) {for (const el of els) {// … e.g. `el.setAttribute("data-initialized", true)`}}const observer = new MutationObserver((mutationRecords) => {const els = mutationRecords// all added nodes.flatMap((mutationRecord) => {return Array.from(mutationRecord.addedNodes)})// just the ones of interest.filter((addedNode) => {return // … e.g. `return addedNode.hasAttribute("data-my-attribute")`});// initialize elements of interest that were added to the pageinitialize(els);})// observe changes to `root` and to its childrenobserver.observe(root, {childList: true,subtree: true,});// initialize elements of interest available at page loadinitialize(root./* … e.g. `root.querySelectorAll("[data-my-attribute]")` */);}initializeAndObserve();
Accessible CSS-Only Light/Dark Toggles (with JS persistence as progressive enhancement)
CSS’s :has() can obviate JS
Add high-performance search to static sites with PageFind
The concise guide to using Pagefind for your static site’s search functionality.
Writing zsh tab completions can be straightforward
How I add tab completion for the zsh command line