I've an iframe containing few elements and all I want to do is to have an outline over the elements wherever I hover/click on the iframe. Like so:
As you can see in the image, I clicked on an h1 element and an outline has been shown so that I get to know what I'm selecting.
Ofcourse I don't want to mess-up with the code inside the iframe so I've the outlining div placed outside the iframe and set it to position: absolute. Then it's using getBoundingClientRect() to get the width, height and top from the iframe so that the div would be of same size and position too.
The problem is, it's getting the values right but not rendering properly.
For example, if you click on the button then here's how it looks like:
As you can see, the width is short from the right. I inspected the elements too, everything has right values: The width of the button is 72.61px and the width of the div is 72.6125 as well but still it's shorter from the right. (Also, if you zoom in, it gets fixed and if you zoom in again, it gets like this again) That seems super strange to me...
This only seems to be in Chrome but not in Firefox...
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test</title>
<style>
html {
height: 100%;
}
body {
padding: 20px 20px 20px 20px;
height: 100%;
margin: 0px;
}
/* Helper divs */
div#hover-selector,
div#click-selector {
box-sizing: border-box;
display: none;
pointer-events: none;
border: 1px solid rgb(76, 120, 255);
position: absolute;
border-radius: 0px;
background-color: transparent;
}
</style>
<script src="script.js" defer></script>
</head>
<body>
<div
id="frame_container"
style="position: relative; width: 100%; height: 100%; overflow: hidden"
>
<iframe
frameborder="0"
title="Project"
id="frame"
style="width: 80%; height: 80%"
></iframe>
<div id="click-selector"></div>
<div id="hover-selector"></div>
</div>
</body>
</html>
page.html: (the code inside the iframe)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style id="visually-default-stylesheet">
* {
box-sizing: border-box !important;
}
*:focus {
outline: none !important;
}
html {
scroll-behavior: smooth !important;
}
/* Remove default margin */
html,
body,
h1,
h2,
h3,
h4,
p,
li,
figure,
figcaption,
blockquote,
dl,
dd {
margin: 0;
}
h1 {
font-family: "Open Sans", sans-serif;
font-size: 32px;
font-weight: 500;
color: #000000;
margin-bottom: 10px;
width: auto;
}
p {
font-family: "Open Sans", sans-serif;
font-size: 15px;
font-weight: 500;
color: #3d5565;
line-height: 28px;
margin-bottom: 10px;
width: auto;
}
button {
font-size: 12px;
font-family: "Open Sans", sans-serif;
font-weight: 600;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 12px;
padding-right: 12px;
border-radius: 6px;
background-color: #0f172a;
color: white;
border: 0;
margin-bottom: 10px;
}
/* Set body's default width and height */
body {
padding: 10px;
min-height: 100vh;
user-select: none;
}
</style>
</head>
<body>
<h1>Heading</h1>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus non
impedit, sequi veniam deleniti culpa odit cumque eligendi ea rem,
quibusdam, ullam asperiores atque delectus cupiditate reprehenderit
distinctio pariatur temporibus.
</p>
<button>Click me</button>
</body>
</html>
script.js:
const click_selector = document.getElementById("click-selector");
const hover_selector = document.getElementById("hover-selector");
const iFrame = document.getElementById("frame");
function calculateRect(element, selector) {
let rect = element.getBoundingClientRect();
selector.style.width = rect.width + "px";
selector.style.height = rect.height + "px";
selector.style.top = rect.top + "px";
selector.style.left = rect.left + "px";
}
let clickedElement;
iFrame.src = "page.html";
iFrame.addEventListener("load", () => {
let iFrameDoc = iFrame.contentDocument;
pageHeight = iFrame.contentWindow.innerHeight;
iFrameDoc.addEventListener("click", (e) => {
clickedElement = e.target;
calculateRect(clickedElement, click_selector, true);
click_selector.style.display = "block";
});
iFrameDoc.addEventListener("mouseover", (e) => {
hoveredElement = e.target;
calculateRect(hoveredElement, hover_selector);
hover_selector.style.display = "block";
});
// updating selector style on window resize
window.addEventListener("resize", (e) => {
if (clickedElement) {
calculateRect(clickedElement, click_selector);
hover_selector.style.display = "none";
}
});
});
Edit:
If I change line-height: 18px it works and stops the quirky behavior. I don't know why. But the question still remains, how line-height is influencing such behavior...
Also, each PC produces this behaviour at different zoom levels. Some at 100, some at 110 and so on... and some don't even have this.


I'm quite sure it has something to do with decimal values for
pxunits. as 1 pixel is a single light on the screen. it almost never ends well if you count on'em.I was going to recommend the use of
Math.ceil()as suggested in the comments. but notflooringthetop&left... Yet, at some point you gonna face bigger problem than a 1px offset:As mentioned in the
getBoundingClientRect()docs the function returns the width & height including the padding and the border width.But
Once you set
box-sizing: border-boxas visible in your iframe code, this will NO Longer be the case.getBoundingClientRect()will now only includes the width/height and the padding but not the border width.What I recommand is using the CSS property
outlineas you already getting the element from the iframe why not just add the
outlineto the style (more preciselyoutline-color,outline-styleandoutline-widthseperatly) in case you dont want to mess with original style you can store the initial values, set the new outline then onblurreset them back.ouline docs
This approach is better because: 1 this is why the property is invented for, 2 you don't need to worry about overlaying other elements that By The Way may not give you same appearence if they don't have the same display (example: div with display block may not exactly fit onto a table with display table even though they have same widths, heights and paddings
One Last Thing to NOTE
in this code
you should keep in mind that
e.targetis always the deepest child. so if some how you have an icon in your button. example: font-awesome icon<i class="fas fa-plus"></i>. then thee.targetwill be the<i>tag NOT thebuttonwhen you hover on the icon (not when you hove on the text).So if you don't have control on the source of the iframe, be ready for some surprises.
Hope this helps :)