Tips For Avoiding Common SVG Pitfalls

Velocity Conf, NYC, October 14th, 2015

@SaraSoueidan, sarasoueidan.com

HELLO

  • Front-End Developer & Writer
  • Author, Codrops CSS Reference
  • Co-author, Smashing Book 5
  • Bird Person

 

Pitfall #1

Presentation Attributes will override inherited styles specified on a parent element.

SVG Presentation Attributes

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="300px" height="300px" viewBox="0 0 300 300">

  <polygon

    fill = "#FF931E"

    stroke = "#ED1C24"

    stroke-width = "5"

    points = "279.1,160.8 195.2,193.3 174.4,280.8   117.6,211.1 27.9,218.3 76.7,142.7 42.1,59.6 129.1,82.7 197.4,24.1 202.3,114 "/>

</svg>

Presentation Attributes and The Cascade

  • Presentation attributes are overridden by any style declarations (inline, <style> or external).

 

  • Presentation attributes override default UA styles and inherited styles.

Styles lower in the diagram override those above them

Presentation Attributes and The Cascade

<style>
    circle {fill: orange;} 
</style>

<svg version="1.1" viewBox="0 0 400 400">
    <g fill="yellow">
        <circle cx="100" cy="100" r="100" fill="blue" />
        <!-- ... -->
    </g>
</svg> 
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display: none;">

   <symbol id="icon-twitter" viewBox="0 0 35 35">
      <!-- Twitter icon markup -->
   </symbol>
    
   <symbol id="icon-github" viewBox="0 0 35 35">
      <!-- Github icon markup -->
   </symbol>
   
   <!-- more icons here... -->
</svg>

real-world use case: sprites

<svg class="icon-twitter">
  <use xlink:href="#icon-twitter"></use>
</svg>

real-world use case: sprites

<svg class="icon-twitter">
  <use xlink:href="icons.svg#icon-twitter"></use>
</svg>

Does not work in IE. Works in Edge.

real-world use case: sprites

Presentation Attributes and The Cascade

<style>
    use {fill: orange;} 
</style>

<svg version="1.1" viewBox="0 0 400 400">
   <symbol id="myCirc" viewBox="0 0 32 32">
        <circle cx="100" cy="100" r="100" fill="blue"/>
    </symbol>

    <use xlink:href="#myCirc"...></use>
</svg> 

This is common in reusable SVG icons in icon sprites.

Pitfall #1: The Solution

circle {
    fill: inherit;
    stroke: inherit;
}

Force the browser into inheriting the styles by taking advantage of the cascade and the CSS `inherit` keyword

P.S. Make sure you set this rule _before_ any new style declarations in the CSS.

svg * {
   all: inherit;
}

Extreme Measure

Note that this will:

1. reset all the SVG attributes that can be set in CSS except transforms for now  (which is good considering the use case, cz u would want to change styles but not transformations + the latter are chained into a matrix!)

2. reset _all_ of those properties — so if u don't specify an alternate value, it will default to browser defaults.

3. This is great for overriding and "bare-boning" SVG elements to allow you to control them completely from the CSS (use case: an SVG whose innards you can't control)

MORE ABOUT Styling Contents Of <use>

Further Styling Control w/ CSS Variables

<svg class="bird">
  <use xlink:href="#bulbul"></use>
</svg>
use {
    color: gray;
    fill: currentColor; /* this will be the base fill color inherited by all elements inside <use> */
    
    /* define variables here */
    --secondaryColor: yellow;
    --tertiaryColor: black;
}

Continued

HTML

CSS

Further Styling Control w/ CSS Variables

<symbol id="bulbul" viewBox="0 0 300 200">
    <path id="body" d="…" />
    <path id="tail" style="fill: ..." fill="var(--tertiaryColor)" d="…" />
    <path id="head" style="fill: ..." fill="var(--tertiaryColor)" d="…" />
   <path id="vent" style="fill: ..." fill="var(--secondaryColor)" d="…" />
</symbol>

SVG

Pitfall #2

Links (<a>) inside an SVG embedded as an <object> will open inside the boundaries of that <object>, not in the main page. 

(—predictably so, considering that <object> is similar in functionality to <iframe>.)

The 6 SVG embedding techniques

<img src="logo.svg" alt="Company Logo" />
.el {background: url(mySVG.svg);}
<object type="image/svg+xml" data="mySVG.svg">
<!--fallback (avoid double downloads!)-->
</object>
<embed type="image/svg+xml" src="mySVG.svg" />
<iframe src="mySVG.svg">
<!--fallback-->
</iframe>
<svg><!--SVG content--></svg>
<object type="image/svg+xml" data="mySVG.svg">
    <img src="fallback.png" alt="..." />
</object>

Don't do this:

Do this instead:

<object type="image/svg+xml" data="mySVG.svg">
    <div id="fallback"></div>
</object>
#fallback {
    background-image: url('fallback.png');
    /* ... */
}

#7

<picture>
    <source type="image/svg+xml" srcset="path/to/image.svg">

    <img src="path/to/fallback.png" alt="...">
</picture>

Better fallback and art direction options.

Example Scenario

<a role="link" tabindex="0" 

xlink:href="..." 

xlink:title="..." 

xlink:target="_parent" 

...> Visit Page </a>

        

Specify the target of the link using the xlink:target attribute

The Solution

Remember to...

  1. Set the namespace on the root <svg> when you have links inside the SVG (including internal links).   xmlns:xlink="http://www.w3.org/1999/xlink"
  2. Set the namespace if you are creating and appending the attributes to elements using JavaScript.                                        var xlinkns = “http://www.w3.org/1999/xlink”;
     anchor.setAttributeNS(xlinkns, ‘href’, ‘…’ );

Pitfall #3

Setting `fill: none` on path area will disable pointer events on that area.

 

Example use case: Paths With Fills

pointer-events: visiblePainted;

The given element can be the target element for pointer events when the ‘visibility’ property is set to visible and when the pointer is over a "painted" area. The pointer is over a painted area if it is over the interior (i.e., fill) of the element and the ‘fill’ property has an actual value other than none or it is over the perimeter (i.e., stroke) of the element and the ‘stroke’property is set to a value other than none.

The Reason

Set the fill to "transparent" instead of "none"

<path ... fill="transparent" ...></path>

The Solution

OR

Change the value of the `pointer-events` property on the element.


visiblePainted | visibleFill | visibleStroke | visible |
painted | fill | stroke | all | none | inherit

 

Check out what these values mean in the specification.

Pitfall #4

Changing the transform origin value halfway through a sequence of animation will cause unsightly jumps of the affected element.

In simple terms, once you scale or rotate an SVG element and then change its transformOrigin, the newtransformOrigin is aligned to where it would have been according to the elements un-transformed state. This forces the element to be re-positioned and then the transforms are applied – leading to some awkward results. 

(Source)

The Problem

Image credit: GreenSock. (http://greensock.com/svg-tips)

The SOlution?

Use JavaScript to gain full control and manually do the math and transformations needed to avoid this.

OR

Use GSAP.

GSAP helps avoid, overcome and word around browser bugs and inconsistencies when animating SVGs.

ABOUT SVG Animation Performance

  • Most browsers don't GPU-accelerate SVG elements. (GSAP can't change that.)
  • "SVG is lightweight and resolution-independent, but that also can be costly when it comes to performance because rather than just shoving rasterized pixels around (which GPUs are really good at), browsers have to calculate the geometry/paths on each frame."
  • Performance is usually worst in mobile. :(

Pitfall #5

Cropping the SVG using the viewBox attribute can yield unwanted visible overflow.

The SVG viewBox Attribute

  • It goes in the <svg> declaration.. (among other elements).
  • It establishes a user coordinate system on the SVG.
  • It is made up of four components: "vx vy width height".
  • These components specify the origin and dimensions of the user coordinate system.
  • By changing the origin of the viewBox, we can crop or extend the SVG canvas.

Cropping The SVG using the viewBox Attribute:

The PRoblem

LIVE DEMO'ING

Pitfall #5:

The Solution

Change the aspect ratio of the <svg> (the viewport) to match that of the new viewBox.

OR

Clip the overflow out using clip-path

<svg ... clip-path="url(#clipper") viewBox="vx vy width height">
  <defs>
      <clipPath id="clipper">
          <rect x="vx" y="vy" width="100%" height="100%"></rect>
      </clipPath>
  </defs> 
  <!-- other svg content & styles -->
</svg>

This trick can also be used to visualise and troubleshoot viewBox problems.

You might want to reposition and/or scale the viewBox after clipping/cropping it. Do that using the preserveAspectRatio attribute.

Read

Everything you need to know about the SVG viewport, viewBox and preserveAspectRatio: 

http://sarasoueidan.com/blog/svg-coordinate-systems 

(Includes an interactive demo.)

Pitfall #6

CSSOM methods produce unexpected values when retrieving an SVG element's bounding box properties.

CSSOM Methods

CSSOM's `getClientRects()` and `getBoundingClientRect()` method(s) can be used to get the top, right, bottom and left offset coordinates of an element and the width and height of the element's bounding rectangle.

 

CSSOM Methods Used on SVG Elements

...can yield unexpected results.

getBoundingClientRect()

expected result

Use getBBox() instead of the CSSOM Methods

var svgElement = document.getElementById('el');
bbox = svgElement.getBBox();

console.log( bbox.x ) ;
console.log( bbox.y ) ;
console.log( bbox.width ) ;  
console.log( bbox.height ) ; 

The Solution

Important Note

[getBBox()] returns the tight bounding box in current user space (i.e., after application of the ‘transform’ attribute, if any) on the geometry of all contained graphics elements, exclusive of stroking, clipping, masking and filter effects).

Pitfall #6:

Extra Tip

Use an element's bounding box properties (bbox.x, bbox.y, bbox.width, bbox.height) to define the viewBox area when cropping an SVG to that element.

viewBox="x y width height"

viewBox="bbox.x bbox.y bbox.width bbox.height"

Pitfall #7

SVG lacks the concept of relative positioning. i.e. You cannot position elements relative to each other like with HTML.

SVG elements are not governed by a box model.

Pitfall #7:

A Workaround

  • You can nest <svg>s.
  • Each <svg> establishes a coordinate system for its content.
  • The position and dimensions of the <svg> can be retrieved from getBBox() of the element you want to position relative to.
  • That <svg> can then be used to contain and position your element(s).

NESTING SVGs

The inner <svg> gets its properties from the nest’s bounding box properties, and the bird goes INSIDE the <svg> and is positioned relative to it.

Root <svg>

Thank You!