Estoy luchando un poco tratando de determinar la ubicación actual y el tamaño de un elemento dentro del DOM. He reunido un fragmento para ilustrar un sistema basado en tarjetas en el lado derecho de la pantalla.
El comportamiento que estoy tratando de construir es que cuando haga clic en una de esas tarjetas, se agregará otra tarjeta (finalmente debajo, pero arriba por ahora) que volará hacia la esquina superior izquierda de la pantalla antes de llenar el espacio disponible.
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="https://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>
Lo que estoy haciendo es bastante básico, tomo el elemento en el que se hizo clic, mido su rectángulo delimitador y agrego un nuevo elemento al body
con el mismo tamaño y posición:
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");
});
he estado leyendo sobre getBoundingClientRect() y parece hacer lo que quiero de acuerdo con las especificaciones, simplemente no está haciendo lo que espero que haga aquí, ya que el ancho/alto están desactivados, y Firefox ni siquiera puede obtener la izquierda correcta. ¿Esta función simplemente está rota (lo que me sorprendería) o parte de mi CSS está rompiendo de alguna manera esta función nativa?
Debo agregar que aquí hay una captura de pantalla de los resultados en diferentes navegadores. IE es, con mucho, el más cercano, pero aún parece tener problemas con los valores inferiores/derechos.
ian
Bueno, estoy muy confundido, pero logré que todo funcionara como quería. Cambié el cálculo para tener en cuenta el relleno, el margen y los bordes basándome en un pequeño trabajo de conjetura y modificando algunos estilos para verificar que todo seguía funcionando. Esto me dio el siguiente cálculo:
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;
Curiosamente, cuando intenté conectar esto a mi aplicación, no funcionó en absoluto. Sacando algo del relleno y terminé con:
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="https://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>
-
¡Tenga en cuenta que algunos navegadores devuelven un objeto rect de solo lectura! Bastante molesto, ¿no? 🙁
– oriadam
28 de abril de 2016 a las 17:17
-
¿De dónde sacaste el valor de relleno?
– Anders Linden
31 de enero de 2017 a las 7:55
-
@AndersLindén buena pregunta, no recuerdo bien. Creo que simplemente usé
d3.select(element).style("padding")
por ejemplo y luego analizó la cadena resultante.– Ian
31 de enero de 2017 a las 9:15
Encontré el mismo problema, pero en mi caso, a veces los rectángulos estaban igualmente compensados por un número constante de píxeles. Descubrí que el nodo del cuerpo en sí mismo puede tener cierto desplazamiento en relación con la ventana gráfica, que debe ajustar cuando adjunta cualquier elemento al cuerpo. Ver el siguiente código:
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");
});
Mencionaste que el elemento medido sea animado. (vuela hacia la esquina superior izquierda de la pantalla antes de llenar el espacio disponible).
Si está haciendo una animación de escala, asegúrese de medir el elemento después de que termine la animación.
getBoundingClientRect()
devuelve las coordenadas del elementopuede ignorar el margen (5), el borde (2) y el relleno (15, 5) establecidos en el elemento (los valores entre paréntesis son los que está usando en la tarjeta “nombre”).– Teemu
10 de marzo de 2015 a las 14:56
@Teemu: Eso es útil y no lo había leído. ¿Dónde encontró esas exclusiones?
– Ian
10/03/2015 a las 15:00
¿Te refieres a los valores? Simplemente abrió Firebug y seleccionó el elemento de stacksnippet y hizo clic en la herramienta “Diseño” para mostrar el posicionamiento del diseño.
– Teemu
10 de marzo de 2015 a las 15:01
@Teemu: no, ¿dónde has leído que el margen, el relleno y el borde están excluidos del rectángulo?
– Ian
10 de marzo de 2015 a las 15:02
@Teemu: supongo que también, ¿conoce una solución de navegador cruzado para calcular esto? ¿O tengo que escribir el mío?
– Ian
10 de marzo de 2015 a las 15:05