getBoundingClientRect 返回错误的结果

Posted

技术标签:

【中文标题】getBoundingClientRect 返回错误的结果【英文标题】:getBoundingClientRect returning wrong results 【发布时间】:2015-05-12 01:34:26 【问题描述】:

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

我正在尝试构建的行为是,当您单击其中一张卡片时,将添加另一张卡片(最终在下方,但现在在顶部),它将飞到屏幕的左上角在填充可用空间之前。

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>

我正在做的是相当基本的,抓住点击的元素,测量它的边界矩形,并以相同的大小和位置向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");
);

我一直在阅读有关 getBoundingClientRect() 的信息,它似乎按照规范执行了我想要的操作,只是没有按照我的预期执行,因为宽度/高度都关闭了,Firefox 甚至不能让左边正确。这个函数是被简单地破坏了(这会让我感到惊讶)还是我的一些 CSS 以某种方式破坏了这个原生函数?

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

【问题讨论】:

getBoundingClientRect() 返回元素的坐标,它可以忽略设置为元素的边距(5)、边框(2)和内边距(15, 5)(值在括号是您在“姓名”卡中使用的内容)。 @Teemu:这很有帮助,我没有读过 - 你在哪里遇到了这些排除? 你的意思是价值观?刚刚打开 Firebug 并从 stacksn-p 中选择元素并单击“布局”工具以显示布局定位。 @Teemu - 不,你在哪里读到边距、填充、边框被排除在矩形之外? @Teemu - 我猜你也知道计算这个的跨浏览器解决方案吗?还是我必须自己写? 【参考方案1】:

好吧,我非常困惑,但设法让事情按我的意愿工作。我根据一些猜测更改了计算以考虑填充、边距和边框,并修改了一些样式以验证它仍然有效。这给了我以下计算:

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;

奇怪的是,当我尝试将其插入我的应用程序时,它根本不起作用。取出一些填充物并最终得到:

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>

【讨论】:

请注意,有些浏览器会返回一个只读的 rect 对象!很烦人,不是吗? :( 你从哪里得到的填充值? @AndersLindén 好问题,我不太记得了。我想我只是简单地使用了d3.select(element).style("padding"),然后解析出结果字符串。【参考方案2】:

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

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

【讨论】:

以上是关于getBoundingClientRect 返回错误的结果的主要内容,如果未能解决你的问题,请参考以下文章

getBoundingClientRect 返回错误的结果

js中getBoundingClientRect()方法详解

Element.getBoundingClientRect()

Element.getBoundingClientRect()

[转] getBoundingClientRect判断元素是否可见

js中getBoundingClientRect( )方法