Javascript getBoundingClientRect 返回错误的结果

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28966678/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:40:19  来源:igfitidea点击:

getBoundingClientRect returning wrong results

javascripthtmlcssd3.js

提问by Ian

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.

我正在努力确定 DOM 中元素的当前位置和大小。我已经整理了一个片段来说明屏幕右侧的基于卡片的系统。

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 bodywith the same size and position:

我正在做的是相当基本的,抓住被点击的元素,测量它的边界矩形并添加一个body具有相同大小和位置的新元素:

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?

我一直在阅读有关getBoundingClientRect() 的内容,它似乎按照规范做我想做的事情,但它没有按照我的预期做,因为宽度/高度都关闭了,Firefox 甚至无法到达左边正确的。这个函数是简单地被破坏了(这会让我感到惊讶)还是我的一些 CSS 以某种方式破坏了这个原生函数?

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.

我应该在这里补充一下是在不同浏览器中关闭结果的屏幕截图。IE 是迄今为止最接近的,但似乎仍然与底部/右侧值作斗争。

enter image description here

在此处输入图片说明

采纳答案by Ian

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 thought 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>

回答by iLaurens

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:

我遇到了同样的问题,但在我的情况下,有时矩形都被恒定数量的像素相等地偏移。我发现 body 节点本身可以​​有一些相对于视口的偏移,当你将任何元素附加到 body 时,你应该调整它。请参阅以下代码:

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");
});