Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

getBoundingClientRect returning wrong results

Tags:

I'm struggling a little trying to determine the current location and size of an element within the DOM. I've put together a fragment to illustrate a card based system down the right hand side of the screen.

The behavior that I'm trying to build is that when you click on one of those cards, another card will be added (ultimately underneath, but on top for now) which will fly out to the top left corner of the screen before filling the available space.

d3.selectAll("attribute-card").on("click", function (d) {       var rect = this.getBoundingClientRect();     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.right - rect.left) + "px")              .style("height", (rect.bottom - rect.top) + "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>

What I am doing is fairly basic, grab the clicked element, measure it's bounding rectangle and adding a new element to the body with the same size and position:

d3.selectAll("attribute-card").on("click", function (d) {     var rect = this.getBoundingClientRect();    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.right - rect.left) + "px")             .style("height", (rect.bottom - rect.top) + "px")             .style("position", "absolute"); }); 

I've been reading about getBoundingClientRect() and it seems to do what I want according to the spec, it's just not doing what I expect it to here as the width/height are all off, and Firefox can't even get the left correct. Is this function simply broken (which would surprise me) or is some of my CSS somehow breaking this native function?

I should add here is a screenshot of the results being off in different browsers. IE is by far the nearest but still seems to struggle with the bottom/right values.

enter image description here

like image 833
Ian Avatar asked Mar 10 '15 14:03

Ian


People also ask

Why is getBoundingClientRect not working?

The "getBoundingClientRect is not a function" error occurs for multiple reasons: calling the getBoundingClientRect() method on a value that is not a DOM element. placing the JS script tag above the code that declares the DOM elements.

What does getBoundingClientRect return?

The getBoundingClientRect() method returns the size of an element and its position relative to the viewport. The getBoundingClientRect() method returns a DOMRect object with eight properties: left, top, right, bottom, x, y, width, height.

Is getBoundingClientRect slow?

Call getBoundingClientRect() It's relatively fast when you have a small number of elements. But it's getting to be slower and forcing a reflow when the number of elements starts to rise dramatically, or when calling multiple time.

Does getBoundingClientRect include margin?

margin is not included.


2 Answers

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>
like image 143
Ian Avatar answered Oct 05 '22 01:10

Ian


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"); }); 
like image 40
iLaurens Avatar answered Oct 04 '22 23:10

iLaurens