How to Transform SVG Elements

So far, we mainly used absolute positions to define elements on the SVG canvas. This can sometimes be tricky. Let’s look at the head of this lamp in this example. We need to know where the end of the arm is to position it correctly, and the whole head is tilted. And what if we need to make a slight adjustment? Like changing the rotation of one of the arms.

We can also define each part in its own space so that they are relative to an anchor point. Then, move and rotate them to their correct place. In this example, we build this lamp in a way that allows us to easily adjust the position of the arm and the head of the lamp.

You can morph the two lamps above to each other by dragging their head. You can adjust their height by dragging the head vertically and tilt the head by dragging it horizontally. We will cover how to make this lamp interactive in the Interaction chapter.

How to Rotate and Translate SVG Elements

The base of the lamp

Lets start with the easy part. We create the lamp’s base with two ellipses.

0, 0
100, 175 100, 170 100, 175 100, 170
200, 200
<svg width="200" height="200">
<ellipse
cx="100"
cy="175"
rx="30"
ry="5" />
<ellipse
cx="100"
cy="170"
rx="30"
ry="5"
fill="gray" />
</svg>

The arm of the lamp

Now, let’s start building up the arm of the lamp. We draw it in its own space independent of its actual position and direction. We go from the 0,0 coordinate and draw a straight line upwards with the path element. And add a circle for the joint.

How to Draw Basic Paths with SVG

For now, most of this is invisible. The 0,0 coordinate is at the top left corner of the canvas, and the arm is pointing upwards. We move it to the correct position in the next step.

0, 0
0, 0 0, 0 0, -70 0, 0 0, 0 0, -70
200, 200
<svg width="200" height="200">
<!-- 1st arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

We can move the arm into position by grouping the elements together and using the transform property. We use the translate function to move the origin to the 70, 170 position.

70 170 0,0 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70
200, 200
<svg width="200" height="200">
<g transform="translate(70, 170)">
<!-- 1st arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
</g>
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

Transformations are relative to the origin of the element. Initially this was the origin of the viewBox, the top-left corner. By applying the translate function we also moved the origin to the 70, 170 position. As a result, we can rotate the arm around the joint with the rotate function.

0,0 -25° 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70
200, 200
<svg width="200" height="200">
<g transform="translate(70, 170)">
<g transform="rotate(-25)">
<!-- 1st arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
</g>
</g>
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

Now let’s add the second piece of the arm. We can do this by nesting another g element inside the previous ones. We move this group to the end of the first arm by applying another translate transformation (we move it vertically by -70, that matches the length of the first arm).

Building up the arm this way has two benefits. First, we can define the line with the same attributes as the previous one. The arm doesn’t need to know where its absolute position is on the canvas. We simply define a line that goes from the 0,0 position straight up to 0,-70.

0 -70 0,0 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70
200, 200
<svg width="200" height="200">
<g transform="translate(70, 170)">
<g transform="rotate(-25)">
<!-- 1st arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
<g transform="translate(0, -70)">
<!-- 2nd arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
</g>
</g>
</g>
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

The second benefit of defining the arms this way is that we can rotate the second arm piece around the joint by applying a rotate transformation. Now lets turn back this second arm to balance out the lamp. We wrap the second arm piece into another group and apply another rotate transformation.

0,0 50° 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70
200, 200
<svg width="200" height="200">
<g transform="translate(70, 170)">
<g transform="rotate(-25)">
<!-- 1st arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
<g transform="translate(0, -70)">
<g transform="rotate(50)">
<!-- 2nd arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
</g>
</g>
</g>
</g>
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

Now lets add the third piece that connects the arm to the head of the lamp. We can do this by nesting another g element inside the previous ones. We move this group to the end of the second arm by applying another translate transformation (we move it vertically by -70, that matches the length of the second arm).

0,0 45° 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -30 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -30
200, 200
<svg width="200" height="200">
<g transform="translate(70, 170)">
<g transform="rotate(-25)">
<!-- 1st arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
<g transform="translate(0, -70)">
<g transform="rotate(50)">
<!-- 2nd arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
<g transform="translate(0, -70)">
<g transform="rotate(45)">
<!-- 3rd arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -30"
/>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

Finally, let’s add a placeholder for the lamp’s head. For now let’s just add a placeholder that we are going to replace in the next step.

0,0 -90° 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -30 -10, -10 0, 0 0, 0 0, -70 0, 0 0, 0 0, -70 0, 0 0, 0 0, -30 -10, -10
200, 200
<svg width="200" height="200">
<g transform="translate(70, 170)">
<g transform="rotate(-25)">
<!-- 1st arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
<g transform="translate(0, -70)">
<g transform="rotate(50)">
<!-- 2nd arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -70"
/>
<g transform="translate(0, -70)">
<g transform="rotate(45)">
<!-- 3rd arm -->
<circle r="5" />
<path
class="arm"
d="M 0 0 L 0 -30"
/>
<g transform="translate(0, -30)">
<g transform="rotate(-90)">
<!-- Head -->
<rect
x="-10"
y="-10"
width="20"
height="30"
rx="5"
/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

The head of the lamp

To simplify things, lets start sketching the head of the lamp as a separate SVG. We will put everything together in the next step. The origin here represents the anchor point that will be connected to the arm.

First we add a gold circle slightly below the anchor point. This will be our light bulb.

-60, -60
0,0 0, 25 0, 25
120, 120
<svg
width="200"
height="200"
viewBox="-60 -60 120 120"
>
<circle
cx="0"
cy="25"
r="10"
fill="gold"
/>
</svg>

Note that these images appear bigger than in the final result because the width and height do not match the size defined by the viewBox.

Then, we create the back side of the lamp’s shade from two quadratic Bézier curves. This one comes before the circle because it is behind the light bulb. Hover over the coordinates in the code or on the image to see how they are positioned.

-60, -60
0,0 -30, 30 0, -20 30, 30 0, 35 -30, 30 0, 25 -30, 30 0, -20 30, 30 0, 35 -30, 30 0, 25
120, 120
<svg
width="200"
height="200"
viewBox="-60 -60 120 120"
>
<path
d="
M -30 30
Q 0 -20 30 30
Q 0 35 -30 30"
fill="gray"
/>
<circle
cx="0"
cy="25"
r="10"
fill="gold"
/>
</svg>

Then we create the front part of the lamp’s shade from two other quadratic Bézier curves. This shape has almost the same values as the back side, except the bottom curve is bending upwards.

-60, -60
0,0 -30, 30 0, -20 30, 30 0, 35 -30, 30 0, 25 -30, 30 0, -20 30, 30 0, 25 -30, 30 -30, 30 0, -20 30, 30 0, 35 -30, 30 0, 25 -30, 30 0, -20 30, 30 0, 25 -30, 30
120, 120
<svg
width="200"
height="200"
viewBox="-60 -60 120 120"
>
<path
d="
M -30 30
Q 0 -20 30 30
Q 0 35 -30 30"
fill="gray"
/>
<circle
cx="0"
cy="25"
r="10"
fill="gold"
/>
<path
d="
M -30 30
Q 0 -20 30 30
Q 0 25 -30 30"
/>
</svg>

Finally, we add a rounded rectangle to finish the head of the lamp.

-60, -60
0,0 -30, 30 0, -20 30, 30 0, 35 -30, 30 0, 25 -30, 30 0, -20 30, 30 0, 25 -30, 30 -10, -10 -30, 30 0, -20 30, 30 0, 35 -30, 30 0, 25 -30, 30 0, -20 30, 30 0, 25 -30, 30 -10, -10
120, 120
<svg
width="200"
height="200"
viewBox="-60 -60 120 120"
>
<path
d="
M -30 30
Q 0 -20 30 30
Q 0 35 -30 30"
fill="gray"
/>
<circle
cx="0"
cy="25"
r="10"
fill="gold"
/>
<path
d="
M -30 30
Q 0 -20 30 30
Q 0 25 -30 30"
/>
<rect
x="-10"
y="-10"
width="20"
height="30"
rx="5"
/>
</svg>

Putting it all together

Now let’s put together, the base, the arm and the head of the lamp.

<svg width="200" height="200">
<g transform="translate(70, 170)">
<g transform="rotate(-25)">
<!-- 1st arm -->
<path d="M 0 0 L 0 -70" class="arm" />
<circle r="5" />
<g transform="translate(0, -70)">
<g transform="rotate(50)">
<!-- 2nd arm -->
<path d="M 0 0 L 0 -70" class="arm" />
<circle r="5" />
<g transform="translate(0, -70)">
<g transform="rotate(45)">
<!-- 3rd arm -->
<path d="M 0 0 L 0 -30" class="arm" />
<circle r="5" />
<g transform="translate(0, -30)">
<g transform="rotate(-90)">
<!-- Head -->
<path
d="M -30 30 Q 0 -20 30 30 Q 0 35 -30 30"
fill="gray"
/>
<circle cx="0" cy="25" r="10" fill="gold" />
<path
d="M -30 30 Q 0 -20 30 30 Q 0 25 -30 30"
/>
<rect
x="-10"
y="-10"
width="20"
height="30"
rx="5"
/>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
<!-- Base -->
<ellipse cx="100" cy="175" rx="30" ry="5" />
<ellipse cx="100" cy="170" rx="30" ry="5" fill="gray" />
</svg>
.arm {
stroke: black;
stroke-width: 7;
stroke-linecap: round;
}

In the Interaction example we are going to add interactivity to this lamp. We are going to add event handlers to adjust the height and the tilt of the head of the lamp.