ππΈοΈ
7/16/2025 9:41AM
New digital garden theme :)
Start menu here...
Thanks for visiting my website! Or as I like to call it, my web desktop. This is a space that I play around with web technologies, share my thoughts and my half-baked projects. Feel free to explore by clicking around.
Want to follow along? You are going to need some basic familiarity with JavaScript, html, css, you will need a code editor and have the files served from a webserver. Due to iframe restrictions, this won’t work locally.
From my An experimental take on the retro website frames… disappearing iframes with vanilla-js, html, and css post we know have a way of using html pages as page fragments using iframes and then making those iframes disappear. Let’s tackle a small proof of concept website using this method. We want the website to have the navigation links and footer to load in from iframes.
So let’s start with a navigation fragment. Since my website is using the small classless css library Simple.css we’re going to structure the main navigation like they intended. Which is basically a nav
tag in a header
tag. Simple enough. Let’s create _nav.html
and use our starting fragment template. Don’t know what’s needed in the fragmentRedirect.js
file? Check out the previous post or code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<script src="fragmentRedirect.js" type="text/javascript"></script>
</head>
<body>
</body>
</html>
Next we’re going to add in the navigation and the links to the pages of the website.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<script src="fragmentRedirect.js" type="text/javascript"></script>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/cats.html">Cats</a>
<a href="/fish.html">Fish</a>
<a href="/chickens.html">Chickens</a>
</nav>
</body>
</html>
The next step is to load our navigation into our home page so let’s create the index.html
file for our website including the disappearingFrame.js
script we made previously. Note the aria-live
and aria-busy
tags I used to mark for screen-readers where the content will be changing. This time I used aria-live="polite"
to notify the user, since navigational elements are pretty important.
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A framed* website</title>
<link rel="stylesheet" href="./style.css" />
<script src="disappearingFrame.js" type="text/javascript"></script>
</head>
<body>
<main>
<header aria-live="polite">
<h1>A framed<sup>*</sup> website</h1>
<iframe aria-busy="true"
src="/_navigation.html"
title="main navigation links"
onload="disappear(this)"></iframe>
</header>
</main>
</body>
</html>
This is what have so far, but if you’re following along with your own code you might have noticed something as it loaded. A flash of white and the size of the header shrinking and expanding as we showed the iframe and then removed it after adding in the elements. Let’s fix that.
Luckily the white flash has been solved before and StackOverflow was quick to point me in the right direction. It actually does something similar to what we did with iframes. It creates a script tag to hide the iframe and then when everything is loaded it re-shows the iframe. Why do that in JavaScript and not use css? Because if a user has JavaScript disabled the iframe will remain visible, which is what we want. We also don’t need to make the iframes visible at the end, because we remove them when we are done copying the content. Let’s add that to our disappearingFrame.js
file. Notice it’s using the IIFE pattern. Here’s what the script file looks like now:
// Hide the white flash of an iframe loading
(function () {
let div = document.createElement('div');
let ref = document.getElementsByTagName('base')[0] ||
document.getElementsByTagName('script')[0];
div.innerHTML = '<style> iframe { visibility: hidden; } </style>';
ref.parentNode.insertBefore(div, ref);
})();
// Load an iframes content into the DOM
function disappear(frame) {
let body = (frame.contentDocument.body || frame.contentDocument);
let children = [...body.children];
for(let child of children) {
frame.before(child);
}
frame.remove();
}
That got rid of the flash… but what about the height issue? Since the height is variable based on what you’re inserting the best way to deal with it is to set the height of the iframe to the height of the content or the parent container with some css. Here I set the height of the header
with a style tag. I added a little dummy content and this is what we have for index.html
.
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A framed* website</title>
<link rel="stylesheet" href="./style.css" />
<script src="disappearingFrame.js" type="text/javascript"></script>
<style> body > header { height: 229px } </style>
</head>
<body>
<header aria-live="polite">
<h2>A framed<sup>*</sup> website</h2>
<iframe aria-busy="true"
src="/_navigation.html"
title="main navigation links"
onload="disappear(this)"></iframe>
</header>
<main>
<header>
<h1>My favorite pets</h1>
<p>This website is dedicated to some of the wonderful pets
I've had over the years.</p>
</header>
</main>
</body>
</html>
Next we’ll add the footer, which is also a fragment. This one is easy.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<script src="fragmentRedirect.js" type="text/javascript"></script>
</head>
<body>
<p>Built with π by heyloura</p>
</body>
</html>
So the final index.html file is this
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A framed* website</title>
<link rel="stylesheet" href="./style.css" />
<script src="disappearingFrame.js" type="text/javascript"></script>
<style> body > header { height: 229px } </style>
</head>
<body>
<header aria-live="polite">
<h2>A framed<sup>*</sup> website</h2>
<iframe aria-busy="true"
src="/_navigation.html"
title="main navigation links"
onload="disappear(this)"></iframe>
</header>
<main>
<header>
<h1>My favorite pets</h1>
<p>This website is dedicated to some of the wonderful pets
I've had over the years.</p>
<figure>
<img alt="Orange tabby cat in the center of a lit Christmas tree"
src="/photos/christmas_cat.jpg" />
<figcaption>
This is the cat who tried his best to knock down my Christmas tree.
</figcaption>
</figure>
</header>
</main>
<footer aria-live="off">
<iframe aria-busy="true"
src="/_footer.html"
title="the footer credits of the website"
onload="disappear(this)"></iframe>
</footer>
</body>
</html>
Which results in this:
We should probably indicate what page the user is on. But how can we do that if the navigation is in an iframe? Good question! Turns out you can, assuming you can use a little JavaScript, but we’ll need to adjust a few things first.
Let’s test if we can pull a script tag and have it run when the content is pulled in from the iframe. Let’s adjust the _navigation.html
file and put a script at the bottom. Also let’s make sure we don’t run it in the iframe, only outside of it by checking if window.top === window.self
that way we can test if it works.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<script src="fragmentRedirect.js" type="text/javascript"></script>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/cats.html">Cats</a>
<a href="/fish.html">Fish</a>
<a href="/chickens.html">Chickens</a>
</nav>
<script>
<!-- Don't run code when in the iframe -->
if(window.top === window.self) {
alert('Hello World!');
}
</script>
</body>
</html>
Uh oh, that didn’t work. Turns out just inserting a script tag doesn’t work because of security concerns. Maybe that’s something the parent page can take care of when it’s pulling in the elements? Let’s give it a shot.
What we need to do is detect if the element we are going to insert is a script
tag. Luckily we have a property that tells us that, element.tagName
and then we can do the trick we used to suppress the white flash of an iframe loading.
So here are the change to the disappearingFrame.js
file:
// Hide the white flash of an iframe loading
(function () {
let div = document.createElement('div');
let ref = document.getElementsByTagName('base')[0] ||
document.getElementsByTagName('script')[0];
div.innerHTML = '<style> iframe { visibility: hidden; } </style>';
ref.parentNode.insertBefore(div, ref);
})();
// Load an iframes content into the DOM
function disappear(frame) {
let body = (frame.contentDocument.body || frame.contentDocument);
let children = [...body.children];
for(let child of children) {
if(child.tagName === 'SCRIPT'){
let script = document.createElement("script");
script.text = child.innerHTML;
document.head.appendChild( script ).parentNode.removeChild( script );
} else {
frame.before(child);
}
}
frame.remove();
}
Sweet! We got the alert that time. This means we can include scripts in our fragments and then execute them. This opens up possibilities I’ll explore in the next post.
So to mark our page as the current page in the navigation we can piggyback on the accessibility tag to mark the current page aria-current="page"
attribute. Plus our stylesheet already styles anchor elements that have that attribute. But now we have a choice add the attribute during the iframe load or once it’s in our parent page. To keep things simple and scoped, I’m going to set the attribute from within the iframe. We’ll just need to swap around the condition of the iframe. Note: when you pull in the script tag to the parent document, the document is the parent. So you’ll need to be careful with using selectors.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<script src="fragmentRedirect.js" type="text/javascript"></script>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/cats.html">Cats</a>
<a href="/fish.html">Fish</a>
<a href="/chickens.html">Chickens</a>
</nav>
<script>
// check that we are in the iframe.
if(window.top != window.self) {
let url = document.referrer.substring(document.referrer.lastIndexOf('/') + 1);
let navs = document.querySelectorAll('a');
for (let nav of navs) {
if(nav.getAttribute('href') == `/${url}` ||
(nav.getAttribute('href') == '/' && url == 'index.html')) {
nav.setAttribute('aria-current','page')
}
}
}
</script>
</body>
</html>
What we are doing, is that if we are in the iframe, grab the anchor tags and loop through them. Check to see if the end part of the url matches the anchor tag href
. We can get the url string from the document.referrer
but only if the iframe source is on the same primary domain as the parent page. You can click between the pages to see the highlighted navigation anchor change. Pretty sweet!
Now that we got something built, let’s see how it does if we turn JavaScript off. Everything looks fine, though the iframe pieces are not styled the same. This can be taken care of by adding a bit of styling to the head
section of the iframe and in the parent setting an appropriate width and height. But what happens if you click to change a page in the navigation? Disaster!
Turns out, the solution to this is pretty easy. Remember how we’re checking if the iframe is the top window in our JavaScript? You can tell anchor tags to open in the top window, by giving them the right target target="_top"
so let’s make those changes.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow" />
<script src="fragmentRedirect.js" type="text/javascript"></script>
</head>
<body>
<nav>
<!-- For navigation elements make sure you add target="_top" to the anchors -->
<a href="/" target="_top">Home</a>
<a href="/cats.html" target="_top">Cats</a>
<a href="/fish.html" target="_top">Fish</a>
<a href="/chickens.html" target="_top">Chickens</a>
</nav>
<script>
if(window.top != window.self) {
let url = document.referrer.substring(document.referrer.lastIndexOf('/') + 1);
let navs = document.querySelectorAll('a');
for (let nav of navs) {
if(nav.getAttribute('href') == `/${url}` ||
(nav.getAttribute('href') == '/' && url == 'index.html')) {
nav.setAttribute('aria-current','page')
}
}
}
</script>
</body>
</html>
And done!
Using our disappearing iframe trick we can build a basic website where the navigation and footer are in separate html pages. I’ve also shown how you can pull in Javascript from these iframes and execute it on the parent page. You can check out the result here: code - preview.
You can use this technique to build a basic website and split frequently repeated portions into their own fragments. Things like navigation, footers, page headers or other repeated content. The method is pretty simple and removes some of the major drawbacks the retro frame system people used in the early web. But it’s also a little more powerful than that, we now have a system that we can create a “component” with… styles, html, and css all in one place without the need for a component framework, or the overhead, of something like Svelte, Vue or React. To challenge how far this can hold up I’m going to build a basic productivity app with this method. So stay tuned.
I have a lot of interests and this place is a catch all. If you prefer to only follow a couple topics then check out my feeds page for category specific RSS feeds.