Skip to content

Instantly share code, notes, and snippets.

@jan-swiecki
Created April 13, 2025 13:00
Show Gist options
  • Save jan-swiecki/db2866ddd06e8c701b16f076239ab266 to your computer and use it in GitHub Desktop.
Save jan-swiecki/db2866ddd06e8c701b16f076239ab266 to your computer and use it in GitHub Desktop.
side-by-side-iframe-sync-scroll.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Side-by-Side HTML Viewer</title>
<style>
/* Ensure the body takes the full height */
html, body {
margin: 0;
padding: 0;
height: 100%;
}
/* Use Flexbox for a side-by-side layout */
.container {
display: flex;
height: 100%;
}
/* Each iframe will take half the width of the container */
.container iframe {
flex: 1; /* Each iframe takes equal space */
border: none; /* Remove default border */
height: 100%;
overflow: auto; /* Enable scroll if needed */
}
</style>
</head>
<body>
<div id="container" class="container">
<iframe class="scrollable" src="file1.html" title="HTML File 1"></iframe>
<iframe class="scrollable" src="file2.html" title="HTML File 2"></iframe>
</div>
<script>
// Sync scroll / sync scrollbars / sync scroll bars (for later grepping this solution)
// Source:
// - https://phuoc.ng/collection/html-dom/synchronize-scroll-positions-between-two-elements/
// - https://chatgpt.com/share/67fafd76-2ac0-8001-9575-b377be903e05
const randomInteger = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
document.addEventListener("DOMContentLoaded", async () => {
const container = document.getElementById("container");
const elements_ = [...container.querySelectorAll(".scrollable")];
let elements = [];
for(const ele_ of elements_) {
const ele = await new Promise((resolve) => {
ele_.addEventListener("load", () => {
const iframeDoc = ele_.contentDocument || ele_.contentWindow.document;
resolve(iframeDoc);
});
});
elements.push(ele);
}
const syncScroll = (scrolledEle, ele) => {
if(scrolledEle.documentElement) scrolledEle = scrolledEle.documentElement;
if(ele.documentElement) ele = ele.documentElement;
const scrolledPercent = scrolledEle.scrollTop / (scrolledEle.scrollHeight - scrolledEle.clientHeight);
const top = scrolledPercent * (ele.scrollHeight - ele.clientHeight);
const scrolledWidthPercent = scrolledEle.scrollLeft / (scrolledEle.scrollWidth - scrolledEle.clientWidth);
const left = scrolledWidthPercent * (ele.scrollWidth - ele.clientWidth);
ele.scrollTo({
behavior: "instant",
top,
left,
});
};
const handleScroll = (e) => {
new Promise((resolve) => {
requestAnimationFrame(() => resolve());
});
const scrolledEle = e.target;
elements.filter((item) => item !== scrolledEle).forEach((ele) => {
ele.removeEventListener("scroll", handleScroll);
syncScroll(scrolledEle, ele);
window.requestAnimationFrame(() => {
ele.addEventListener("scroll", handleScroll);
});
});
};
elements.forEach((ele) => {
ele.addEventListener("scroll", handleScroll);
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment