Jump to content
Search Community

SVG Gotchas!

Carl test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Masking vs Clipping

 

I'm not sure this is a gotcha, but it's definitely a confusing matter that has been very time-consuming for me. Dropping it here to maybe save someone else.

 

Here's a

See the Pen ALoWpk by nerdmanship (@nerdmanship) on CodePen

using the same path for both masking and clipping to compare the two. I expected the exact same result, but the mask clearly includes all the attributes and the clipPath is ignoring the stroke attributes and just renders the inside of the path (like a fill would).

 

"For the clipping, every path inside the clipPath is inspected and evaluated together with its stroke properties and transformation."

Mozilla Docs: Clipping and Masking

 

I don't think the doc is wrong, but it's definitely confusing. I understand this as the stroke properties will be included.

 

"A clipping path is conceptually equivalent to a custom viewport for the referencing element."

Mozilla Docs <clipPath>

 

This, to me, means never do anything fancy with clipPath. Just use it to define a "viewport".

  • Like 3
Link to comment
Share on other sites

  • 6 months later...

On my recent handwriting demo (

See the Pen MpLVvO by PointC (@PointC) on CodePen

), I hit a little oddity in Chrome involving the bounding box of a SVG group and incorrect data.

 

To demonstrate this issue, I’ve created a 100x100 SVG and added a 100x100 rectangle for reference as a background. I then dropped in  a circle and added 2 points to make it into a blob. These two elements are grouped. Both elements are within the 100x100 size of the viewBox and the group. You would expect a group size of 100x100 and x/y positions of 0, but Chrome reports back odd size and position data. Firefox, IE and Edge all calculate everything as you’d expect.

 

u46pxTD.png

 

Chrome seems to be calculating the group BBox based on the child elements plus the Bezier handle positions of the path(s).

 

wpaXl9e.png

 

You can check it for yourself:

 

See the Pen vxqgrr by PointC (@PointC) on CodePen

 

This can cause problems when trying to align the group to a motion path or animate it with precision to a new x/y position. Just something to watch for when creating your vector artwork.

 

Happy tweening

  • Like 7
Link to comment
Share on other sites

Hello Craig (PointC),

 

I believe this to be the webkit getBBox() svg bug report for what you are describing:

It looks like it affects SVG <path> elements with cubic bezier curves that calculate more than what the actual getBBox() is.

 

It shows as RESOLVED FIXED in the above bug report, but with webkit that doesn't mean anything since webkit is a groundhogs day of fun feelings!

 

UPDATE:

 

Here is a new Chrome webkit bug report ticket regarding this webkit bug, - ticket status is STARTED

:)

  • Like 3
Link to comment
Share on other sites

Thanks Jonathan. :)

 

If they ever did have that fixed, they must have brought it back for Throwback Thursday.

 

Strangely, the BBox of the path itself is accurate. It's only when it's calculated in a group or as part of the entire SVG that it's wrong.  :blink:

  • Like 1
Link to comment
Share on other sites

Everything about getBBox is a gotcha. For those that don't know what it is, it's a method on SVG elements that will return its position and size...

 

var bounds = someElement.getBBox(); // => { x: 0, y: 0, width: 100, height: 100 }

 

Sounds pretty useful as positioning is an important part of animation. The problem is that it reports its bounding box without any transforms applied. What does that mean? If you have an element at 0,0 and tween it to 100,100 using x and y, and then get it's bounding box, it will say it's still at 0,0. This goes for all transforms, x, y, scale, rotation, etc. Not very useful considering most animations use transforms.

 

Things get even more confusing with group <g> elements, which don't have a fixed bounds like other elements. They are calculated based on the size and position of their children. If you call getBBox on a group, and it has transformed children inside of it, it will take into account those transforms. 

 

Look at this example. The green box is the bounds of the group. The red box is the bounds of the blue square. As you can see, the blue square is nowhere near the red box. It uses transforms to calculate some things, but not others, which can be really confusing. 

 

VAyQF7s.png

 

Check it out for yourself....

 

 

 

 

 

Another confusing thing about bounds is that they are axis-aligned (AABB), meaning they are parallel to the x and y axis. Take a look at what happens when you rotate an element. The blue boxes are the original bounds of the element, but they are no longer aligned, so a new bounds is created around them to make them aligned. This causes the bounds to separate and move away from the element. Look at how the green boxes don't even touch the elements on the left and right. 

 

 

6DmO3hL.png

 

 

You really need to see it in action to fully understand how it works. The red boxes are the bounds getBBox reports. The blue box is the transform being applied to the red box. And the green box is the transformed bounds, which is calculated by drawing an axis aligned box around the blue box. Notice how large the transformed bounds become at the end. That can cause stuff like hit testing with Draggable to fail.

 

 

See the Pen ZKGjRo by osublake (@osublake) on CodePen

 

So there's a little info about bounds and why getBBox isn't that useful. Well, unless you want to find the original position and size of your element. But it's not a total loss. It just needs to be improved, so I made a little getBBox helper function that will calculate transformed bounds, which is what's needed to get correct positioning.  

 

 

/**
 * @param {SVGElement} element - Element to get the bounding box for
 * @param {boolean} [withoutTransforms=false] - If true, transforms will not be calculated
 * @param {SVGElement} [toElement] - Element to calculate bounding box relative to
 * @returns {SVGRect} Coordinates and dimensions of the real bounding box
 */
function getBBox(element, withoutTransforms, toElement) {
  
  var svg = element.ownerSVGElement;
  
  if (!svg) {
    return { x: 0, y: 0, cx: 0, cy: 0, width: 0, height: 0 };
  }
  
  var r = element.getBBox(); 
  
  if (withoutTransforms) {
    return {
      x: r.x,
      y: r.y,
      width: r.width,
      height: r.height,        
      cx: r.x + r.width / 2,
      cy: r.y + r.height / 2
    };
  }
  
  var p = svg.createSVGPoint(); 
      
  var matrix = (toElement || svg).getScreenCTM().inverse().multiply(element.getScreenCTM()); 

  p.x = r.x;
  p.y = r.y;
  var a = p.matrixTransform(matrix);

  p.x = r.x + r.width;
  p.y = r.y;
  var b = p.matrixTransform(matrix);

  p.x = r.x + r.width;
  p.y = r.y + r.height;
  var c = p.matrixTransform(matrix);

  p.x = r.x;
  p.y = r.y + r.height;
  var d = p.matrixTransform(matrix);

  var minX = Math.min(a.x, b.x, c.x, d.x);
  var maxX = Math.max(a.x, b.x, c.x, d.x);
  var minY = Math.min(a.y, b.y, c.y, d.y);
  var maxY = Math.max(a.y, b.y, c.y, d.y);
    
  var width = maxX - minX;
  var height = maxY - minY;
  
  return {
    x: minX,
    y: minY,
    width: width,
    height: height,        
    cx: minX + width / 2,
    cy: minY + height / 2
  };
}

 

The function takes 3 parameters. The first one is required, which is the element you want to get the bounds for. The second one, calculateWithoutTransforms, will calculate the bounds without any transforms if set to true. And the third parameter is an element to calculate the bounds relative to. By default it uses the SVG document the element is in, which is usually what you want, as it's kind of like the global space.

 

I also have another version that will work around the Chrome bug that PointC came across. I was able to get around the bug by manually calculating the bounds of all the children in a group element instead of calling getBBox.

 

So here's a pen of the original version...

 

See the Pen YVXvgR by osublake (@osublake) on CodePen

 

 

And the one with the Chrome workaround...

(EDIT - looks like Chrome fixed the bug so this isn't necessary anymore)

 

See the Pen BRNPrL by osublake (@osublake) on CodePen

 

 

And one last tip. You can set outlines on SVG elements in your CSS in Chrome. This is really helpful as it will let you see where stuff is, like a group element, which is usually invisible. That makes positioning so much easier. You can even see it incorrectly apply an outline on paths inside a group, just like with PointC issue.

 

g {
  outline: 1px dashed red;
}

path {
  outline: 1px solid blue;
}

 

 

Putting it all together:

https://codepen.io/osublake/pen/ZxVmyx

 

 

 

 

  • Like 7
Link to comment
Share on other sites

Excellent info Blake. :)

 

I'm with Carl - please write that up for a CodePen blog post. The more people over there that realize how smart you are and that you hang out here in GreenSock Land, the better it could be for the GS community.

 

Bring on the new Club Members. Just think how full your $1/answer tip jar could be with a bunch of new GSAP users.  ;)

  • Like 2
Link to comment
Share on other sites

  • 1 month later...
  • 3 months later...

Yay! I get to contribute here :D

 

But not without a hat tip to @OSUblake

 

You can nest an SVG into another SVG - That's quite handy for a number of reasons,  one quick example would be: You have a super complex drawing with some text inside it. You can have an inline SVG that holds the text and then, nest the other SVG in with an <image>. That'll keep the text reachable with CSS and JS without having a wall of code for the rest of the drawing.

 

However - If you end up nesting an SVG inside another one you will not be able to transform the nested root SVG element (for now).

 

https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform#Elements

 

See the Pen 11a819dd170f2f0079307289f60cefa5 by dipscom (@dipscom) on CodePen

 

  • Like 3
Link to comment
Share on other sites

While we wait for SVG 2 and the ability to transform nested SVGs, you can currently fake a scale() for a nested SVG. By animating the width/height/x/y attributes we can create the illusion of animating scale(). If you don't have those attributes on the nested SVG, you can add them via a TweenMax.set(). You can then gain some control and fake the scale along with changing the point of origin.

 

See the Pen BwwEyK by PointC (@PointC) on CodePen

Not a perfect solution, but if you only want to scale a nested SVG from a specific point, it works.

:)

 

  • Like 2
Link to comment
Share on other sites

  • 6 months later...

Having watched Chris Gannon's video I'd like to suggest a slightly different workflow.

After some trial and error I find the easiest is to keep all the graphic editing in Illustrator, do export as an SVG file, that leave it unchanged. Then in the html page I load the svg file using ajax, with a recipe found here: http://plnkr.co/edit/LneUEK?p=preview .

I did a CodePen to illustrate the method, with a "this works / this does not work" example, to show the utility of those lines of code:

See the Pen vRzYQd by fvila (@fvila) on CodePen

 

I've got rid of "_1" ids by attributing names to the layers in the Illustrator's layer panel. Or at least, to any object I will need to animate or manipulate later.

 

The advantage is that you don't have to do any of the copy-pasting described by Chris. You can keep the exported SVG file in the text editor for reference. Each time you do another export the text editor asks you whether you want to load the new version, and all you have to do is click OK.

 

The only drawback I see is that it doesn't work in CodePen. If you want to share your code, you have to do the copy-paste the SVG into the html section of Codepen. But you would typically do that once or twice, not all the time as you would when you are trying to get things to work.

  • Like 1
Link to comment
Share on other sites

  • 4 weeks later...

SVG Mask Animations and a slight rotation

 

This has popped up several times in the forum so I thought I'd drop it into this thread. Some of the browsers are getting fussy with SVG mask animations. This is especially true of DrawSVG stroke masks. You should add a slight rotation to the mask element as it animates to force the browser to redraw the mask. Right now Edge is the biggest offender, but the way Chrome has been adding bugs with each release, it may not be long before they call it a feature.

 

If you look at this pen in all the browsers, you'll see that most of them handle both mask animations just fine, but in Edge the right one will not work correctly. Try resizing the window in Edge and then using  DevTools to scrub the timeline. You'll see that right mask get rather funky.

 

See the Pen yjzLLL by PointC (@PointC) on CodePen

 

I've also seen this in Edge with a rectangular gradient mask so my recommendation is to add that slight rotation to any and all mask animations just to be safe. Happy tweening everyone.

 

 

 

  • Like 2
Link to comment
Share on other sites

  • 3 weeks later...
  • 1 year later...

The SVG specifications currently do not support 3D transforms (including perspective, transformPerspective, rotationY, rotationX, rotationZ, translate3d, rotate3d, and matrix3d) on elements within SVGs. Some browsers go rogue and implement them anyway but it isn't reliable to do in a cross-browser way. This is not a limitation with GSAP, it's a limitation with browsers themselves. 

 

You can work around the lack of 3D transform support of elements within SVGs in some cases by 

  1. Faking the transform that you need. For example sometimes rotationYs can be replaced by a scaleX instead.
  2. Applying the transform to a container instead. If you can limit the elements within the SVG to just the ones you want to transform, this is a great approach. For example, applying a rotationY to the <svg> or <div> containing a <path> instead of applying it to the <path> itself.

Use 3D transforms on SVG elements at your own risk. A browser may choose to remove support at any time given it's not currently part of the official SVG specifications.

Link to comment
Share on other sites

  • 2 weeks later...
  • 5 months later...
  • 3 weeks later...

If you want to morph a drip or melt from a straight edge, you'll probably need to add some points to the edge. The problem is that Adobe Illustrator strips the points out of your starting shape when you export because it thinks you don't need them. It's super helpful that way.

 

I have an entire post about this problem and solution here:

https://www.motiontricks.com/organic-morphing-getting-needed-points-from-adobe-illustrator/

 

TL;DR

Don't export your paths. Select the paths (and background rectangle) and copy/paste the data into your code editor. The extra points along the straight edge will remain intact giving you a buttery smooth result. Side by side comparison of stripped points vs all points coming through.

 

See the Pen 5319aa1c56ee55e2978eda9772167c21 by PointC (@PointC) on CodePen

 

  • Like 3
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...