Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getScreenBBox() method to SVGGraphicsElement interface #76

Open
jarek-foksa opened this issue Apr 2, 2016 · 9 comments
Open

Add getScreenBBox() method to SVGGraphicsElement interface #76

jarek-foksa opened this issue Apr 2, 2016 · 9 comments

Comments

@jarek-foksa
Copy link

There is no way to determine the bounding box of an SVG element in the client space.

The generic element.getBoundingClientRect() method, unlike element.getBBox(), does not allow you to specify whether to include stroke and markers in the result. It also does not return a geometric bbox, but rather a bbox of a user bbox that has been transformed to the client space, which is not what you would expect or want in most scenarios.

I would propose to add getScreenBBox() method to the SVGGraphicsElement interface. The method would take the same arguments as the existing getBBox() method. Note that "screen" in the method name would actually mean "client", for the sake of consistency with getCTM()/getScreenCTM().

@BigBadaboom
Copy link
Contributor

Or alternatively, perhaps, add a new property to SVGBoundingBoxOptions?

dictionary SVGBoundingBoxOptions {
  boolean fill = true;
  boolean stroke = false;
  boolean markers = false;
  boolean clipped = false;
  boolean applyTransforms = false;
};

If applyTransforms is true, then the bounding box that is calculated takes into account all transforms on the current and ancestor elements.

@fsoder
Copy link

fsoder commented Apr 2, 2017

This would be something like the below (modulo terrible indentation) using currently speced primitives?

function getScreenBBox(element) {
let ctm = element.getScreenCTM();
let localBBox = DOMQuad.fromRect(element.getBBox({fill: true, stroke: true, markers: true}));
let clientBBox = new DOMQuad(localBBox.p1.matrixTransform(ctm),
localBBox.p2.matrixTransform(ctm),
localBBox.p3.matrixTransform(ctm),
localBBox.p4.matrixTransform(ctm));
return clientBBox.getBounds();
}

@jarek-foksa
Copy link
Author

jarek-foksa commented Apr 2, 2017

@foolip: In order to determine the geometric bbox of a path in the client space you can't just transform paths' user (local) bbox to the client space, this might produce wrong results when the path or its ancestors are transformed.

Instead, you have to determine the coordinates of each path node point, then transform each point to the client space and finally use that to determine the bbox.

Doing this in JS is very slow, I suspect browsers could make it much faster by querying the render tree.

@fsoder
Copy link

fsoder commented Apr 2, 2017

Ah, yes, I missed that part of the issue. Computing the "precise" bounding box in "screen-space" will be costly even if "querying the render tree" though - unless this happen to be stored already (it isn't in Blink for example. There's a cost associated with maintaining this kind of "absolute" state - if there weren't, gBCR et al would likely already return these tight fitting bboxes.)

@AmeliaBR
Copy link
Contributor

AmeliaBR commented Apr 3, 2017

Yes, we already use the simplified approach in other cases when combining transforms and BBox calculations, precisely for the issue of computational complexity.

Currently, if you compute the BBox of a rotated path, or of a <g> that contains a rotated path, you get the bounds in the parent's coordinate space of the path's rotated BBox, not tight bounds for the rotated path.

This CodePen by Sarah Drasner demonstrates the effect clearly.

Therefore, for compatibility, if a getScreenBBox() method was introduced, it would probably follow the steps in @fsoder's comment, although I'd recommend letting the method take the options object as a parameter, rather than assuming certain values.

@jarek-foksa
Copy link
Author

jarek-foksa commented Apr 3, 2017

@AmeliaBR

From my testing pathElement.getBBox() returns a proper geometric minimum bounding box in the user space. I.e. the smallest possible rectangle inside which the path fits in, computed using raw path data (without applying any transforms).

That gist is demonstrating the problem I was talking about earlier - you can't just take a bounding box in the user space and transform its coordinates to some other space - this will produce a bounding box of another bounding box which is not useful and wrong if you stick to the mathematical definition.

Making getScreenBBox() behave this way would in my opinion be inconsistent with how getBBox() works and somehow redundant because this is what element.getBoundingClientRect() already does (without the advanced options though).

BTW, In Blink (and probably WebKit) pathElement.getBBox() seems to be taking control points of bezier segments into account when computing the bbox. Hopefully this is a bug rather than a standard behavior.

@BigBadaboom
Copy link
Contributor

There is a very common request on Stack Overflow to get the bounds of an element including its own transform. IOW returning {x:50, y:0, width:100, height:100} for

<rect width="100" height="100" transform="translate(50,0)"/>

So it would be good to consider this use case in any solution.

So, using my previous suggestion, for example:

dictionary SVGBoundingBoxOptions {
  boolean fill = true;
  boolean stroke = false;
  boolean markers = false;
  boolean clipped = false;
  boolean selfTransform = false;
  boolean ancestorTransforms = false;
};

@fsoder
Copy link

fsoder commented Nov 26, 2017

Having selfTransform and ancestorTransforms means you have the possibility of { selfTransform: false, ancestorTransforms: true } which is a bit of an oddity. So an enumeration (enum ApplyTransforms { "none", "self-only", "self-and-ancestors" }; ... ApplyTransforms applyTransforms = "none" ...) or even a (nullable) Element might be a better choice.

@paradisaeidae
Copy link

Blake/ OSUblake has a great writeup of this issue here: https://greensock.com/forums/topic/13681-svg-gotchas/?page=2&tab=comments#comment-72060

His codepen of a workaround is excellent!

@boggydigital boggydigital added this to the SVG 2.1 Working Draft milestone Jun 11, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants