getBoundingClientRect returning wrong results
Well I'm mightily confused but managed to get the thing working as I wanted. I changed the calculation to take into account padding, margin and borders based on a little guess work, and modifying some styles to verify it all still worked. This gave me the following calculation:
var rect = element.getBoundingClientRect();
rect = {
left: rect.left - margin.left,
right: rect.right - margin.right - padding.left - padding.right,
top: rect.top - margin.top,
bottom: rect.bottom - margin.bottom - padding.top - padding.bottom - border.bottom
};
rect.width = rect.right - rect.left;
rect.height = rect.bottom - rect.top;
return rect;
Oddly though when I tried plugging this into my application it didn't work at all. Taking out some of the padding and ended up with:
rect = {
left: rect.left - margin.left,
right: rect.right - border.right,
top: rect.top - margin.top,
bottom: rect.bottom - border.bottom - border.top
};
rect.height = rect.bottom - rect.top;
rect.width = rect.right - rect.left;
return rect;
function getBoundingRect(element) {
var style = window.getComputedStyle(element);
var margin = {
left: parseInt(style["margin-left"]),
right: parseInt(style["margin-right"]),
top: parseInt(style["margin-top"]),
bottom: parseInt(style["margin-bottom"])
};
var padding = {
left: parseInt(style["padding-left"]),
right: parseInt(style["padding-right"]),
top: parseInt(style["padding-top"]),
bottom: parseInt(style["padding-bottom"])
};
var border = {
left: parseInt(style["border-left"]),
right: parseInt(style["border-right"]),
top: parseInt(style["border-top"]),
bottom: parseInt(style["border-bottom"])
};
var rect = element.getBoundingClientRect();
rect = {
left: rect.left - margin.left,
right: rect.right - margin.right - padding.left - padding.right,
top: rect.top - margin.top,
bottom: rect.bottom - margin.bottom - padding.top - padding.bottom - border.bottom
};
rect.width = rect.right - rect.left;
rect.height = rect.bottom - rect.top;
return rect;
};
d3.selectAll(".card").on("click", function (d) {
var rect = getBoundingRect(this);
var card = d3.select("body")
.append("div")
.attr("class", "card")
.style("background", "transparent")
.style("border", "thin solid red")
.style("left", rect.left + "px")
.style("top", rect.top + "px")
.style("width", rect.width + "px")
.style("height", rect.height + "px")
.style("position", "absolute");
});
html {
height: 100%;
margin: 0;
font-family: Arial;
overflow: hidden;
}
body {
height: 100%;
}
svg {
background: #2c272b;
width: 100%;
height: 100%;
}
.radial-menu .segment {
fill: #3b3944;
}
.radial-menu .segment:hover {
fill: #535060;
}
.radial-menu .symbol {
pointer-events: none;
fill: white;
}
.radial-menu .symbol.icon {
font-family: 'FontAwesome';
}
.beam {
stroke: #fff;
}
.planet circle {
fill: #399745;
stroke: #3b3944;
stroke-width: 0;
stroke-dasharray: 33,11;
}
.planet .related {
fill: none;
stroke: #3b3944;
stroke-dasharray: none;
stroke-width: 25px;
}
.planet text {
fill: #000;
opacity: 0.4;
text-anchor: middle;
pointer-events: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.planet .name {
font-size: 2.5em;
width: 94%;
margin: 125px 0px 0px 10px;
}
.planet.selected text {
fill: white;
opacity: 1;
}
.planet.focused text {
fill: white;
opacity: 1;
}
.moon circle {
fill: #3b3944;
}
.moon:hover {
fill: #535060;
}
.moon text {
fill: white;
text-anchor: middle;
pointer-events: none;
}
.gravity {
stroke: #3b3944;
fill: #3b3944;
stroke-linecap: round;
stroke-width: 2px;
}
.card-list {
background: #2c272b;
position: absolute;
top: 0;
right: 0;
width: 200px;
min-height: 100%;
opacity: 1;
}
.card {
background: #dedede;
border: 2px solid #ebebeb;
margin: 5px 5px 5px 5px;
border-radius: 8px;
padding: 5px 15px 5px 15px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.card .title {
font-weight: bold;
}
.card .summary {
color: #cc8b11;
font-weight: bold;
font-size: 12px;
}
.card .summary .summary-item {
margin: 0;
}
/*# sourceMappingURL=style.css.map */
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<html><head>
<meta charset="utf-8">
<meta name="msapplication-tap-highlight" content="no">
<title name="Business Landscape Explorer Prototype"></title>
<link href="bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="styles/style.css">
<script src="d3.v3.js" charset="utf-8"></script><style type="text/css"></style>
</head>
<body>
<div id="card-list" class="card-list">
<div id="attributes" class="attribute-list" data-bind="foreach: attributes">
<attribute-card params="value: $data"><div class="card attribute-card"> <p class="title" data-bind="text: name">Name</p> <div class="summary" data-bind="foreach: summaries"></div> </div></attribute-card>
<attribute-card params="value: $data"><div class="card attribute-card"> <p class="title" data-bind="text: name">Cost</p> <div class="summary" data-bind="foreach: summaries"> <p class="summary-item" data-bind="text: $data">Average: £9 million</p> <p class="summary-item" data-bind="text: $data">Total: £2,700 million</p> </div> </div></attribute-card>
<attribute-card params="value: $data"><div class="card attribute-card"> <p class="title" data-bind="text: name">Start Date</p> <div class="summary" data-bind="foreach: summaries"> <p class="summary-item" data-bind="text: $data">Earliest: 31st Jan 2007</p> <p class="summary-item" data-bind="text: $data">Latest: 27th Nov 2019</p> </div> </div></attribute-card>
<attribute-card params="value: $data"><div class="card attribute-card"> <p class="title" data-bind="text: name">Enabled</p> <div class="summary" data-bind="foreach: summaries"> <p class="summary-item" data-bind="text: $data">True: 71%</p> <p class="summary-item" data-bind="text: $data">False: 29%</p> </div> </div></attribute-card>
<attribute-card params="value: $data"><div class="card attribute-card"> <p class="title" data-bind="text: name">Status</p> <div class="summary" data-bind="foreach: summaries"> <p class="summary-item" data-bind="text: $data">Red: 11%</p> <p class="summary-item" data-bind="text: $data">Amber: 36%</p> <p class="summary-item" data-bind="text: $data">Green: 41%</p> </div> </div></attribute-card>
</div>
</div>
</body></html>
I encountered the same problem but in my case sometimes the rectangles were all equally offset by a constant number of pixels. I discovered that the body node itself can have some offset relative to the viewport, which you should adjust for when you attach any element to the body. See the following code:
d3.selectAll("attribute-card").on("click", function (d) {
var bodyRect = document.body.getBoundingClientRect(); // Get potential offset of the page's body node
var rect = this.getBoundingClientRect(); // This gives coordinates relative to the viewport, not relative to the body's origin
var card = d3.select("body")
.append("div")
.attr("class", "card")
.style("background", "transparent")
.style("border", "thin solid red")
.style("left", (rect.left - bodyRect.left) + "px") // Correct for the body's offset
.style("top", (rect.top - bodyRect.top) + "px") // Correct for the body's offset
.style("width", (rect.right - rect.left) + "px")
.style("height", (rect.bottom - rect.top) + "px")
.style("position", "absolute");
});