SVG Tricks

Here are some clever or useful things you can do in SVG.

A basic SVG file

This is a complete SVG file. You can copy it and use it as a starting point for your UI.

SVG 1.1

  <?xml version="1.0"?>
  <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
        "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
   <svg xmlns="http://www.w3.org/2000/svg" version="1.1"
         xmlns:xlink="http://www.w3.org/1999/xlink"
        viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" >
    <script type="text/ecmascript">
    <![CDATA[
      // ...here is some ECMAScript, or at least an ECMAScript comment.
    // ]]>
    </script>
    <defs>
      <style type="text/css">
      <![CDATA[
        text {
          text-anchor: middle;
        }
      ]]>
      </style>
    </defs>
    <circle cx="50" cy="50" r="40" />
  </svg>

SVG 1.2

  <?xml version="1.0"?>
  <svg xmlns="http://www.w3.org/2000/svg" version="1.2"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" >
    <script type="text/ecmascript">
    <![CDATA[
      // ...here is some ECMAScript, or at least an ECMAScript comment.
    // ]]>
    </script>
    <defs>
      <style type="text/css">
      <![CDATA[
        text {
          text-anchor: middle;
        }
      ]]>
      </style>
    </defs>
    <circle cx="50" cy="50" r="40" />
  </svg>

This contains a <script> segment (which contains only a comment); a <defs> segment (which contains only a simple CSS stylesheet); and then one element to display, a <circle>.

Note that the SVG 1.1 file has a DOCTYPE header, but the SVG 1.2 file does not.

Another note: In SVG 1.2, the type for <script> elements is supposed to be "batik.html" class=wikipagelink>Batik does not yet support this. (See [Batik bug 35549].)

Deciding what is displayed on top

An SVG document is drawn "back to front". Shapes defined later are drawn on top of shapes defined earlier.

If you want to define a bunch of shapes as a "layer" (in the Photoshop sense), place them all into a group (a <g> tag). An SVG group acts as a layer; again, it is drawn back to front.

Make multiple copies of a template object

SVG has a standard idiom for defining some shape and then "stamping" it multiple times into your document. You create the object inside your <defs> block; shapes defined here are not displayed, only stored for later use.

  <defs>
  <g id="template">
    <rect x="-20" y="-20" width="40" height="40"
      fill="red" />
    <circle cx="0" cy="0" r="23"
      fill="green" />
  </g>
  </defs>

It doesn't have to be a group; it can be any SVG element. Be sure to give it a unique id attribute. It is also a good idea to draw this object at the origin (position 0,0). The example above is centered at the origin.

Then you can use it from the main part of your document:

  <use xlink:href="#template" x="50" y="50" />

The href attribute refers to the id of the template object.

Don't use x/y attributes on a group

For some SVG elements, setting "x" and "y" attributes has exactly the same effect as setting a "transform" attribute with a translation. For example, these two elements produce the same results:

  <use xlink:href="#template"
    x="50" y="50" />
  <use xlink:href="#template"
    transform="translate(50,50)" />

However, this is not true of <g> (group) elements. This:

  <g transform="translate(50,50)">
    <circle cx="0" cy="0" r="23" fill="green" />
  </g>

...is not the same as this:

  <!-- wrong -->
  <g x="50" y="50">
    <circle cx="0" cy="0" r="23" fill="green" />
  </g>

The latter does not produce an error, but the x/y attributes are ignored. If you want to change the position of a group, you must use a "transform" attribute.

Don't use x/y attributes and transform on the same object

This is legal, but the results will usually not be what you wanted. Imagine: you have defined some object, and you want it at a particular location.

  <use xlink:href="#template" x="50" y="50" />

Then you decide it's too big, so you scale it down by half:

  <!-- wrong -->
  <use xlink:href="#template" x="50" y="50"
    transform="scale(0.5)" />

That half-size all right, but it's in the wrong place -- it's at (25, 25) instead of (50, 50). The transform is scaling the position as well as the size.

There are a couple of ways to get around this. One, of course, would be:

  <use xlink:href="#template" x="100" y="100"
    transform="scale(0.5)" />

...but that's confusing, and it will break again if you change your mind about the scale.

A clearer form is this:

  <use xlink:href="#template"
    transform="translate(50,50) scale(0.5)" />

Instead of "x" and "y" attributes, you add a translation (slide motion) to the "transform" attribute. Since the translate is given before the scale, the translate values are understood to be in pre-scaling units.

You could also put the translation on a separate group, encapsulating the original object:

  <g transform="translate(50,50)">
    <use xlink:href="#template"
      transform="scale(0.5)" />
  </g>

Add tooltips to SVG elements

It is possible to add text labels to any SVG element. These labels will appear if the player hovers his mouse pointer over the element.

The primary label is given by a <title> child element:

  <circle cx="30" cy="30" r="10">
    <title>Round button</title>
  </circle>

upload:svg-tricks-tooltip-1.png

A more descriptive label should be put in a <desc> child element:

  <circle cx="30" cy="30" r="10">
    <desc>This makes the game go.</desc>
  </circle>

upload:svg-tricks-tooltip-2.png

It is legal to have both:

  <circle cx="30" cy="30" r="10">
    <title>Round button</title>
    <desc>This makes the game go.</desc>
  </circle>

upload:svg-tricks-tooltip-3.png

Note that mouse hovering is considered to be a pointer event. If you have set pointer-events="none" on an element (see below), tooltips will not appear.

Make text unselectable

By default, <text> elements in an SVG file are selectable. The player's cursor will turn into a text bar over these elements; he can click-and-drag to select a range of the text, or double-click to select the whole thing.

There's nothing wrong with selectable text, but for most game UI elements, it's distracting. (Particularly labelled buttons -- you want an "Okay" button to be pushable, not selectable.) So you will generally want to turn this off. Conveniently (or not), there are several ways to do this.

The easiest method is to put a pointer-events="none" attribute onto each <text> element. This makes your text impervious to mouse action.

  <text
    pointer-events="none"
    x="10" y="10">
      Hello
  </text>

It's a nuisance to apply such an attribute to every single <text> element, so you may instead want to use a stylesheet:

  <defs>
    <style type="text/css">
      <![CDATA[
        text {
          pointer-events: none;
        }
      ]]>
    </style>
  </defs>

This applies to all <text> in your document.

A third option is to set the attribute on a top-level group, and let all of your elements inherit it:

  <g pointer-events="none">
    <text x="10" y="10">
      Hello
    </text>
  </g>

(It's even legal to set the same attribute on your top-level <svg> element, which everything inherits from.)

However, if you do this, you'll be turning off pointer events for all your elements, not just text. All your buttons and so on will stop working. To counter this, you'll have to turn pointer events back on for those elements which should have them.

  <g pointer-events="none">
    <text x="10" y="10">
      Hello

    </text>
    <circle cx="30" cy="30" r="10"
      pointer-events="all"
      onclick="handle_click()" />
  </g>

(This is not necessarily a bad strategy. You'll often have a lot of fiddly SVG objects, even in your buttons and clickable pieces. It saves time and complexity if just one element in each button -- the border shape -- is sensitive to pointer events. By turning them off in a top-level group, and then back on for specific elements, you can achieve this level of control.)

The valid pointer-events values are described in the [SVG spec]. Most often you'll want to use none (no clicks), all (accept clicks inside the shape or on the border stroke), or visible (same as all when the object is visibility="visible", but none when it's visibility="hidden").

See also

SVG Script Tricks