0

This code below places textBox1 at a y-position of 200, but getBoundingClientRect returns a value of 190.

Why?

https://codepen.io/anon/pen/REKayR?editors=1011

<svg id="rootBox" width="500" height="800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

    <rect x="0%" y="0%" width="100%" height="100%" fill="beige" />

    <svg id="textBox1" x="0%" y="200" width="100%" height="25%">
      <rect class="background" x="0%" y="0%" width="100%" height="100%" fill="gray" fill-opacity="0.5" />
      <text class="textGroup" x="0" y="0"><tspan x="50%" dy="-0.25em" text-anchor="middle">tspan line 0</tspan><tspan x="50%" dy="1.5em" text-anchor="middle">tspan line 1</tspan><tspan x="50%" dy="1.5em" text-anchor="middle">tspan line 2</tspan></text>
    </svg>

</svg>


var textBox = $("#textBox1");
var textBBox = textBox[0].getBoundingClientRect();
console.log("The y-pos is: " + textBBox.y);
Crashalot
  • 33,605
  • 61
  • 269
  • 439

2 Answers2

4

.getBoundingClientRect() is part of the generic Element interface, and computes the rectangle in relation to the screen viewport. SVG offer some more specific methods:

  • SVGGraphicsElement.getBBox() computes the bounding box in the local coordinate system the element is drawn in.
  • SVGGraphicsElement.getCTM() computes the transformation matrix getween the local coordinate system and the nearest SVG viewport (a <svg> element, for example).
  • SVGGraphicsElement.getScreenCTM() computes the transformation matrix getween the local coordinate system and the screen viewport.

In addition, the DOMMatrix interface has an .inverse() method, so you can easily compute positions in the opposite direction. (For example, if you transform a mouse event screenx/screenY position with the result of element.getScreenCTM().inverse(), you'll get the mouse position in relation to that element.)

The one thing a bit awkward is that you have to construct a SVGPoint object, which can only be achieved by the SVGSVGElement.createSVGPoint() method on an <svg> element, to have something to apply your matrix to.

As for your question, consider the different return values for the three doordinate systems for the rect inside the inner <svg>:

var textBox = document.querySelector('#textBox1 rect');
var svg = document.querySelector('#rootBox');
var point = svg.createSVGPoint();

var local = textBox.getBBox();
point.x = local.x, point.y = local.y;
console.log("local: ", local.x, local.y);

var nearest = textBox.getCTM();
var point2 = point.matrixTransform(nearest);
console.log("nearest viewport: ", point2.x, point2.y);

var screen = textBox.getScreenCTM();
var point3 = point.matrixTransform(screen);
console.log("screen viewport: ", point3.x, point3.y);
<svg id="rootBox" width="500" height="800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

    <rect x="0%" y="0%" width="100%" height="100%" fill="beige" />

    <svg id="textBox1" x="0%" y="200" width="100%" height="25%">
      <rect class="background" x="0%" y="0%" width="100%" height="100%" fill="gray" fill-opacity="0.5" />
      <text class="textGroup" x="0" y="0"><tspan x="50%" dy="-0.25em" text-anchor="middle">tspan line 0</tspan><tspan x="50%" dy="1.5em" text-anchor="middle">tspan line 1</tspan><tspan x="50%" dy="1.5em" text-anchor="middle">tspan line 2</tspan></text>
    </svg>
</svg>
ccprog
  • 20,308
  • 4
  • 27
  • 44
  • it was so frustrating to deal with this issue, so many lost hours. and you solved it in minutes. thank you. – Crashalot Jan 06 '19 at 22:19
1

getBoundingClientRect takes things like scroll position into account. Any margin or padding on the HTML body would also factor in, but adding to the result rather than subtracting.

If you make sure you aren't scrolled down, and the margins do not factor in, you will get 200:

function log() {
  var textBox = $("#textBox1");
  var textBBox = textBox[0].getBoundingClientRect();
  console.log("The y-pos is: " + textBBox.y);
}
log();
setInterval(log, 1000);
html,
body {
  margin: 0;
  padding: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<svg id="rootBox" width="500" height="800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

    <rect x="0%" y="0%" width="100%" height="100%" fill="beige" />

    <svg id="textBox1" x="0%" y="200" width="100%" height="25%">
      <rect class="background" x="0%" y="0%" width="100%" height="100%" fill="gray" fill-opacity="0.5" />
      <text class="textGroup" x="0" y="0"><tspan x="50%" dy="-0.25em" text-anchor="middle">tspan line 0</tspan><tspan x="50%" dy="1.5em" text-anchor="middle">tspan line 1</tspan><tspan x="50%" dy="1.5em" text-anchor="middle">tspan line 2</tspan></text>
    </svg>

</svg>
Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
  • Is there an equivalent function that takes scrolling, margin, and padding into account? or do you have to manually compute this? thanks of your help! – Crashalot Dec 20 '18 at 04:49
  • @Crashalot I think it depends on what you are trying to get the Y value relative to. If you know that, you can get the Y value of that element, and subtract from the Y value of the other element, and you will have your answer. – Alexander O'Mara Dec 20 '18 at 04:51
  • For instance, trying this Codepen still yields the wrong value even when using the offset function of jQuery: codepen.io/anon/pen/REKayR?editors=1111 – Crashalot Jan 06 '19 at 19:06
  • @Crashalot jQuery intentionally doesn't fully support SVG, it's primarily a DOM library, so it's probably related to that. – Alexander O'Mara Jan 06 '19 at 19:09