svgdotjs / svg.js

The lightweight library for manipulating and animating SVG
https://svgjs.dev
Other
10.95k stars 1.07k forks source link

BBox only works when svg is inside the DOM #231

Closed peteruithoven closed 9 years ago

peteruithoven commented 9 years ago

Currently the bbox() method relies on the native clientLeft, clientTop, clientWidth, clientHeight properties. But these don't seem to return a valid value when the shape isn't added to the DOM.

I'm using svg.js to convert all sorts of svg files into something very general that I can send to a digital fabrication device (lasercutter, cnc, plotter, 3D printer etc) so I do not necessarily want to draw the svg while I'm converting it. I'm using bbox() through the toPath plugin.

I'd like to figure out a way to work around this and retrieve these values without "drawing" the svg.

peteruithoven commented 9 years ago

Same issue: http://bytes.com/topic/javascript/answers/504541-hidden-elements-have-zero-clientwidth-clientheight-firefox-there-workaround

I'll try the following soon: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.offsetWidth https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect https://developer.mozilla.org/en-US/docs/Web/API/range.getBoundingClientRect

wout commented 9 years ago

Indeed, there is no geometry if the element is not inside the DOM. I think the only way is to draw the elements in a hidden SVG. You could use the SVG.parser to do that:

https://github.com/wout/svg.js/blob/master/src/svg.js#L69-74

There is an instance of SVG.Doc accessible through SVG.parser.draw. Although make sure you don't call clear() straight on that instance because it will break things. The safest would be to create a group in there to have a safe playground.

Note that getBoundingClientRect() is wrapped by the rbox() method in SVG.js. It is a totally different beast. It returns values based on the appearance of an element rather than its geometry. So you might be in for some unexpected behavior if you are expecting getBBox() values.

peteruithoven commented 9 years ago

I wasn't sure how to use this parser.draw instance and how other code uses this instance, so I used the following code:

        // import svg into a hidden svg element
        var container = document.createElement("div");
        document.body.appendChild(container);
        var svg = SVG(container)
        svg.style('opacity:0;position:fixed;left:100%;top:100%;overflow:hidden')
        var store = svg.svg(content);

        // turn all shapes into paths
        svg.toPath(true);

Thanks for the tips!

wout commented 9 years ago

That works too. Only, if you woud use the parser document you only have one invisible svg floating around. And your code would be more concise:

// import svg into a hidden svg element
var svg = SVG.parser.draw.nested()
var store = svg.svg(content)

// turn all shapes into paths
svg.toPath(true)

If you need it multiple times you could store it there as well:

SVG.parser.sponge = SVG.parser.draw.nested()

Then you can reference it every time you need to import:

// import svg into a hidden svg element
var store = SVG.parser.sponge.clear().svg(content)

// turn all shapes into paths
SVG.parser.sponge.toPath(true)

And with the next release it will be even shorter:

// import svg into a hidden svg element and turn all shapes into paths
SVG.parser.sponge.clear().svg(content).toPath(true)