Clear skies ahead
Luck is relative. Take this website for instance. If you have never stumbled on an error page, you might have never enjoyed the comforting presence of a shy, but helpful character. You don’t have to look for trouble to find it, however.
What fortune then. You don’t even have to wonder too much how to create a similar mascot, nor how to reveal its true colors.
SVG canvas
It’s always nice to start from scratch, and an empty <svg> element. I can’t always figure out the viewBox attribute in the early stages, so I tend to rely on a squared canvas, drawing from the very center.
<svg viewBox="-50 -50 100 100"></svg> Circles
Let’s begin with a single circle.
<circle fill="#ebf3f5" r="11" /> We are going to repeat the shape to create the larger cloud, so it is convenient to define the circle early and then repeat the visual through the <use> element.
<svg viewBox="-20 -20 40 40">
<defs>
<circle id="c" fill="#ebf3f5" r="11" />
</defs>
<use href="#c">
</svg> I prefer to set the color past the <defs> block, so that ultimately, the reference is to a round shape.
-<circle id="c" fill="#ebf3f5" r="11" />
+<circle id="c" r="11" /> This may all seem unnecessary, but in the moment you want to create a slightly different visual — not that that would ever happen — you need to change the one instance to have the changes ripple through the document.
That being said, where to place the circles is up to interpretation, and part of the fun of drawing with SVG.
<g fill="#ebf3f5">
<use href="#c" />
<use href="#c" transform="translate(-12.5 -5)" />
<use href="#c" transform="translate(12.5 -5)" />
<use href="#c" transform="translate(0 -10)" />
<use href="#c" transform="translate(-20 0)" />
<use href="#c" transform="translate(20 0)" />
<use href="#c" transform="translate(-15 10)" />
<use href="#c" transform="translate(15 10)" />
<use href="#c" transform="translate(0 15)" />
</g> There’s a cloud. A bit flat, but convincing.
Shadows
Yes, the mascot at the top of the page certainly looks different. There’s a face, sure, but there’s also a level of depth which is currently nowhere to be seen.
I’ll roll back to a single circle to explain how to cast the shadow with a <mask> element. This is just one of the possible solutions, but leads to a rather flexible setup.
Define the mask right after the circle.
<circle id="c" r="11" />
<mask id="mask">
<!-- -->
</mask> In a mask you draw shapes, similarly to the body of the <svg> element. White shapes, black shapes, and anything in between.
<mask id="mask">
<use href="#c" fill="black" />
</mask> Once you then apply the mask to the actual drawing, you show the portions described as white, while you progressively hide darker and darker areas. The black sections? Those are completely removed from sight. Case in point, reference the mask in the mask attribute on a single instance of the circle.
<use href="#c" mask="url(#mask)" /> I promise you, there is a veritable <svg> element in the page, not just an empty box. You only see the outline because of the mask. Trust me, there’s also a point to the discussion.
Update the mask to:
add a white circle, showing everything
<mask id="mask"> <use href="#c" fill="white" /> </mask>overlap a black circle slightly off center
<mask id="mask"> <use href="#c" fill="white" /> <use href="#c" y="-2" fill="black" /> </mask>
You offset the black circle to create a rather familiar sliver. Our shadow! Feel free to experiment with the handles and create your custom eclipse.
Drag the handles to update the horizontal and vertical offset.
The best thing about this sliver is that it will never exceed the boundaries of the circle.
Remember the unassuming circle defined earlier? Now we can refine the visual with the shadow.
Define an additional shape, this time with a group element.
<defs>
<g id="cc">
<!-- -->
</g>
</defs> Add the first circle, as is.
<g id="cc">
<use href="#c" />
</g> Add another circle, but in the form of our sliver. A black, possibly translucent, sliver.
<g id="cc">
<use href="#c" />
<use href="#c" mask="url(#mask)" fill="black" opacity="0.1" />
</g> Use the new shape in place of the previous circle and there you have it. Perspective.
<g fill="#ebf3f5">
<use href="#cc" />
</g> Too much effort for an exceedingly small change? You’ll be the judge. In the end you have a working shadow. What is more, the syntax is versatile enough to work with lots of bright colors — you need to update just the fill attribute the moment you use the shape.
-<g fill="#ebf3f5">
+<g fill="#eb6a6a"> Cloud
We now have two shapes, c and cc.
For the newly and improved cloud, it is helpful to actually use both: the flat version above, the elaborate circle below.
<g fill="#ebf3f5">
<use href="#c" />
<use href="#c" transform="translate(-12.5 -5)" />
<use href="#c" transform="translate(12.5 -5)" />
<use href="#c" transform="translate(0 -10)" />
<use href="#cc" transform="translate(-20 0)" />
<use href="#cc" transform="translate(20 0)" />
<use href="#cc" transform="translate(-15 10)" />
<use href="#cc" transform="translate(15 10)" />
<use href="#cc" transform="translate(0 15)" />
</g> Faces
It may come to a surprise, but there are actually two versions of the cloud. Let’s start with the more stern character, the one which certainly did not escape your gaze.
Two circles on either side make up the eyes.
<g fill="#acacfa">
<g transform="translate(10 -2.5)">
<circle r="2.5" />
</g>
<g transform="translate(-10 -2.5)">
<circle r="2.5" />
</g>
</g> I like to position the shapes with group elements to change the origin of the coordinate system. It is slightly technical, so you are welcome to skip the note, but if you were to rely on the cx and cy attribute.
<circle cx="10" cy="-2.5" r="2.5" /> If you were to apply a transformation, like a change in scale.
<g transform="scale(0.5)">
<circle cx="10" cy="-2.5" r="2.5" />
</g> You would not scale the circle from its center, but from the point described by the group. You may want to, but in this instance I care to move the origin.
One horizontal line smack in the center is enough to introduce the mouth, and complete the unimpressed expression.
<g fill="#acacfa" stroke="#acacfa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<g transform="translate(0 8)">
<path d="M -2 0 h 4" />
</g>
</g> Now, let’s try to cheer things up with a few shapes. Add two ellipses below the eyes, two colorful cheeks.
<g fill="#f9a3c6">
<g transform="translate(10 2.5)">
<ellipse rx="2.5" ry="1.4" />
</g>
<g transform="translate(-10 2.5)">
<ellipse rx="2.5" ry="1.4" />
</g>
</g> Immediately after the horizontal line add a cheerful mug with a slightly more complex <path> element.
<path d="M -2 0 h 4" />
<path d="M -2 0 v 2 a 2 2 0 0 0 4 0 v -2z" /> Why drawing on top of the previous path? It’ll make sense soon enough. Right now, let’s enjoy the remarkable change brought by these small tweaks.
Changes
As hinted, you don’t see the jolly face, at least not immediately. The plan is to show the somber character by default, and lift the mood only as the cursor enters the <svg> element.
In the <script> tags import the spring function from the svelte/motion module.
<script>
import { spring } from 'svelte/motion';
</script> Create an instance to describe a change scale, with an initial value of 0.
const scale = spring(0, {
stiffness: 0.2,
damping: 0.5
}); Use the value directly in the markup, to scale a few elements into oblivion. Hide the cheeks.
<!-- both ellipses -->
<g transform="scale({$scale})">
<ellipse rx="2.5" ry="1.4" />
</g> Hide the expanded mouth, effectively returning to the first face — not for long, however.
<g transform="scale({$scale})">
<path d="M -2 0 v 2 a 2 2 0 0 0 4 0 v -2z" />
</g> Define helper functions to change the spring’s value: one to reach 1, and have the shapes return, one to move back to 0, and hide them back.
const handleStart = () => {
scale.set(1);
};
const handleEnd = () => {
scale.set(0);
}; Call the functions following the mouseenter and mouseleave events respectively.
<svg
on:mouseenter={handleStart}
on:mouseleave={handleEnd}>
<!-- -->
</svg> And, reap the rewards.
Finishing touches
As with Ray, I hope you won’t mind a couple of extra features, baked in the first version blessing this article.
Bounce more
Always from the svelte/motion module import the tweened function. From the /easing module, on the other hand, refer to a responsive timing function.
<script>
import { tweened, spring } from 'svelte/motion';
import { elasticOut } from 'svelte/easing';
</script> Create an instance with an initial value of 1 and the chosen easing.
const bounce = tweened(1, {
duration: 1000,
easing: elasticOut
}); Wrap the entire composition in a group element to scale everything up.
<g transform="scale({$bounce})">
<!-- mascot -->
</g> Update the tween alongside the spring.
const handleStart = () => {
scale.set(1);
bounce.set(1.1);
};
const handleEnd = () => {
scale.set(0);
bounce.set(1);
}; The effect is nice enough you might just consider additional group elements. Care to have the eyes pop even more?
<!-- both circles -->
<g transform="scale({$bounce})">
<circle r="2.5" />
</g> Touch events
Consider two additional events: touchstart and touchend.
<svg
on:touchstart={handleStart}
on:touchend={handleEnd}>
<!-- -->
</svg> The effect is not exactly the same, but at least you extend the interaction to mobile devices. All that’s needed is a tap — you decide how long to keep smiling.