Squeaky Portraits: Having Fun with the CSS path() Function

Squeaky Portraits: Having Fun with the CSS path() Function – SitePointSkip to main contentFree JavaScript Book!Write powerful, clean and maintainable JavaScript.RRP $11.95 With the Chrome 88 release, we got support for clip-path: path(). That means it now has support in “most” major browsers!
With path(), we’re able to use path definitions for a clip-path. (You catch up on what clip-path is here). These path definition strings are the same as those we can use with the SVG path element. What’s cool about this is that it provides a way to create shapes that before may have meant using SVG. We can even create paths that break without requiring any tricks.
With the increased support came an opportunity to try something fun with it! Let’s make “Squeaky Portraits”! It’s a fun take on using clip-path: path() to clip the viewable area of an element into these “Nickelodeon-esque” splats.

“Squeaky Portraits 👇😅”
Wasn’t done playing with CSS clip-path: path() 😂
Aiming for a splat but settled for a squeaky sound effect 😆
Powered by scoped variables! 💪
(Better with sound 👍)
👉 https://t.co/Nuqyivpm5Y via @CodePen pic.twitter.com/TCCouglKpd
— Jhey 🐻🛠 (@jh3yy) February 12, 2021

Creating a Path
First up, we need our own SVG style path definition string. And in this case, more than one. The neat thing with clip-path is that we can transition them with CSS. As long as the clip-path function and number of nodes are consistent, we can transition.
To make some paths, we can hop in any vector graphic editor. In this case, I’m using Figma. And instead of creating the paths from scratch, we can use a desired “splat” as a foundation. This one looks good!
Splat Example Found Online
The trick here is to create more splats based on the foundation splat. And we need to do this without introducing or removing any nodes. These are the three splats I came up with. But you could make any shapes you like as long as you stick to that rule!
Three Different Splats Built From One Splat
You may notice that the third splat has two blobs that separate off from the main shape. This is fine, because SVG path definitions allow us to do this. We can start a line, close it, and move to another point to start another.
But didn’t I say they needed a consistent number of points? They do. And that’s what we have here! Those two blobs appear for each splat. But the trick is that we can move them behind the rest of the path when they aren’t needed.
Figma showing two blobs behind main path
Once we have our splats, we can export them and grab the path definition strings:

See the Pen 1. SVG Splats by SitePoint (@SitePoint)on CodePen.

Applying Splats
To apply the splats, we’re going to create variables for each path:
.portrait {
–splat: “M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919 180.294…”;
–splattier: “M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919…”;
–splatted: “M232.5 256C225 251 209.5 262.5 224 281.5C232.736 292.948…”;
}

These are the paths we’ve lifted straight out of the exported SVG.
We’re going with the names “splat”, “splattier”, and “splatted”. Naming things is hard. Ha! But take, for example, the “splatted” SVG:

We’re lifting out the d attribute from the path elements and creating CSS variables for them. Next, we need an element to apply these to. Let’s create an element with the class “portrait”:

Next, apply some styling to it:
.portrait {
–splat: “M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919 180.294…”;
–splattier: “M161 188.375C170 193.5 177.919 193.854 186 188.375C197.919…”;
–splatted: “M232.5 256C225 251 209.5 262.5 224 281.5C232.736 292.948…”;
–none: “”;
height: 300px;
width: 300px;
background: #daa3f5;
clip-path: path(var(–clip, var(–none)));
transition: clip-path 0.2s;
}

And we’re good to go! Here’s a demo where you can switch between the different clip states:

See the Pen 2. Applying Clip Splat by SitePoint (@SitePoint)on CodePen.

Note how the shape transitions between the three splat shapes. But, also note how we’ve given our element an explicit height and width. This size matches the dimensions of our SVG exports. This is important. This is the one drawback of using clip-path: path(). It’s not responsive. The path definition is relative to the dimensions of your element. This is the same problem faced by CSS motion paths.
This is fine if we’re mindful of the sizes of things we’re clipping. We could also create different path variables for different viewport sizes. But if you have images that resize in a fluid way, other solutions using SVG are going to be more robust.
Interaction
For our demo, we want the splat to be interactive. We can do this with CSS alone. We can use a scoped CSS variable — –clip — to control the current clip. And then we can update that variable on both :hover and :active. The –active state is triggered when we press our pointer down:
.portrait {
clip-path: path(var(–clip, var(–splat)));
}
.portrait:hover {
–clip: var(–splattier);
}
.portrait:active {
–clip: var(–splatted);
}

Throw that together and we get something like this. Try hovering over the splat and pressing it:

See the Pen 3. Interactive Splat by SitePoint (@SitePoint)on CodePen.

Adding Some Character
Now that we can transition the splat, it needs a little something extra. What if we transform it in those states too?
.portrait {
transition: clip-path 0.2s, transform 0.2s;
transform: scale(var(–scale, 1)) rotate(var(–rotate, 0deg));
}
.portrait:hover {
–scale: 1.15;
–rotate: 30deg;
}
.portrait:active {
–scale: 0.85;
–rotate: -10deg;
}

Using scoped CSS variables to apply a transform, we can add something. Here we update the scale and rotation of our splat. We can experiment with different values and play with different effects here. Translating the element a little could look good?

See the Pen 4. Adding Some Character by SitePoint (@SitePoint)on CodePen.

Adding a Portrait
Now for the fun stuff! I wouldn’t recommend using these pictures of me. But you can if you want, ha! I had this idea that I’d take three silly pictures of myself and have them respond to the user. I got some help and ended up with these three pictures:
Three silly poses
Then we need to put them into the portrait:

That won’t look great. They need some styles:
.portrait {
position: relative;
}
.portrait__img {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
}

Almost there:

See the Pen 5. Getting Portraits in Place by SitePoint (@SitePoint)on CodePen.

How can we show and hide them on :hover and :active. It’s a little verbose, but we can use nth-of-type with display: none:
.portrait__img {
display: none;
}
.portrait__img:nth-of-type(1) {
display: block;
}
.portrait:hover .portrait__img:nth-of-type(1),
.portrait:hover .portrait__img:nth-of-type(3) {
display: none;
}
.portrait:hover .portrait__img:nth-of-type(2) {
display: block;
}
.portrait:active .portrait__img:nth-of-type(1),
.portrait:active .portrait__img:nth-of-type(2) {
display: none;
}
.portrait:active .portrait__img:nth-of-type(3) {
display: block;
}

Why not refactor those styles and group them up? The cascade will kick in and we won’t get the effect we want.

See the Pen 6. Show/Hide Portraits by SitePoint (@SitePoint)on CodePen.

Parallax Icons
We’re getting there, but it looks a little bland. We could create a rudimentary parallax effect if we pulled in an icon. Let’s go with this one.

The trick here is to use an image as a background for our element but size it so that it tiles with background-repeat:
.portrait {
background-image: url(“/code-icon.svg”);
background-color: hsl(10, 80%, 70%);
}

Neat.

See the Pen 7. Icons BG by SitePoint (@SitePoint)on CodePen.

But we want parallax! To get that parallax effect, we can update the background-position in response to pointer movement. And we can map the pointer position against some limit that we define.
Let’s start by creating a utility that generates a mapping function for us. The returned function will give us the result of a value in one range mapped onto another:
const genMapper = (inputLower, inputUpper, outputLower, outputUpper) = > {
const inputRange = inputUpper – inputLower
const outputRange = outputUpper – outputLower
const MAP = input = > outputLower + (((input – inputLower) / inputRange) * outputRange || 0)
return MAP
}

Take a moment to understand what’s happening here. For example, if our input range was 0 to 500 and our output range was 0 to 100, what would the result of calling the returned function be with 250? It would be 50:

genMapper(0, 500, 0, 100)

const inputRange = 500
const outputRange = 100
const MAP = > input = > 0 + (((input – 0) / 500) * 100)

(250 / 500) * 100
0.5 * 100

50

Once we have our utility function to generate the mapping functions, we need a limit to use with it. And we need to generate a mapper for both the horizontal and vertical axes:
const LIMIT = 25
const getX = genMapper(0, window.innerWidth, -LIMIT, LIMIT)
const getY = genMapper(0, window.innerHeight, -LIMIT, LIMIT)

The final part is tying that up to an event listener. We destructure the x and y value from the event and set CSS variables on the portrait element. The value comes from passing x and y into the respective mapping functions:
const PORTRAIT = document.querySelector(‘.portrait’)
document.addEventListener(‘pointermove’, ({ x, y }) = > {
PORTRAIT.style.setProperty(‘–x’, getX(x))
PORTRAIT.style.setProperty(‘–y’, getY(y))
})

And now we have parallax icons!

See the Pen 8. Parallax Icons by SitePoint (@SitePoint)on CodePen.

The Squeak
Last touch. It’s in the title. We need some squeaks. I usually find audio bytes on sites like freesound.org. You can get them in all sorts of places, though, and even record them yourself if you want.
It’s not a bad idea to create an object where you can reference your Audio:
const AUDIO = {
IN: new Audio(‘/squeak-in.mp3’),
OUT: new Audio(‘/squeak-out.mp3’),
}

Then, to play an audio clip, all we need do is this:
AUDIO.IN.play()

We need to integrate this with our portrait. We can use the pointerdown and pointerup events here — the idea being that we play one squeak when we press and another on release.
If a user clicks the portrait a lot in quick succession, this could cause undesirable effects. The trick is to play the desired sound and at the same time, stop the other. To “stop” a piece of Audio, we can pause it and set the currentTime to 0:
PORTRAIT.addEventListener(‘pointerdown’, () = > {
AUDIO.OUT.pause()
AUDIO.IN.currentTime = AUDIO.OUT.currentTime = 0
AUDIO.IN.play()
})
PORTRAIT.addEventListener(‘pointerup’, () = > {
AUDIO.IN.pause()
AUDIO.IN.currentTime = AUDIO.OUT.currentTime = 0
AUDIO.OUT.play()
})

And that gives us a “Squeaky Portrait”!

See the Pen 9. A Squeaky Portrait! by SitePoint (@SitePoint)on CodePen.

That’s It!
That’s how you make “Squeaky Portraits”. But the actionable thing here is having fun while trying out new things.

See the Pen Squeaky Portraits 😅 (clip-path: path()) by SitePoint (@SitePoint)on CodePen.

I could have morphed a couple of shapes and left it there. But why stop there? Why not come up with an idea and have some fun with it? It’s a great way to try things out and explore techniques.
In summary, we:
created the clips
morphed them with transitions
made interactive images
added Audio
created parallax with a mapping utility
What could you do with clip-path: path()? What would your “Squeaky Portrait” look like? It could do something completely different. I’d love to see what you make!
As always, thanks for reading. Wanna see more? Come find me on Twitter or check out the the live stream!
P.S. If you want to grab all the code, it’s here in this CodePen collection.
Jhey makes awesome things for awesome people! He’s a web developer with almost 10 years of experience. Working with and for names such as Eurostar, Uber, NearForm, Barclaycard, ATG, O2, and M&C Saatchi.

Jhey thrives on bringing ideas to life. He takes pride in crafting innovative solutions with polished user experiences. This whilst possessing a keen eye for design, accessibility, and well-structured code.

He’s a CodePen advocate and shares attention-catching demos over on Twitter. New books out now!Get practical advice to start your career in programming!Master complex transitions, transformations and animations in CSS! Latest Remote JobsDevOpsPingthings, Inc.kubernetesdocker

35+ Best Space Fonts (Free & Pro) 2021

Are you looking for a space-themed font to add a futuristic and sci-fi vibe to your designs? Then this collection of space fonts is the best place to start! Space fonts line up with a popular trend that’s taking over the design world, especially in website headers, social media, posters, and print design. In this…

The End of AMP

I worked for a major news publisher and had to get my team to implement this piece of junk.Imagine you have a website and a customer does a google search to find your site and then clicks on your link in the search result. Instead of being taken to your website you are just shown the content within google. That way, the customer does not get to actually visit your site at all.So news outlets pay a lot of marketing/Branding dollars to get people to their sites. They also have commercial agreements to show ads to visitors and they also have paywall funnels on their content. You can imagine how pissed they are when google serves the link to the article and the visitor never makes it to the site.So you might ask why would a publisher bother to implement amp in the first place ? Well because we were told that if we don’t do it then google will penalise our search result ranking by rewarding improved rankings for those outlets that had amp pages.

A Deep Dive Into Eleventy Static Site Generator

But first — let’s quickly review what is meant by a “static site” and then what a generator provides. A static site is composed of static content — as in, the HTML, CSS, assets, and all content are already compiled together before pushing to a website host. This is different from a dynamic site that…

Going Headless: Use Cases And What It’s Good For

Looking back at the years of developing for the web, I’ve used dozens of different CMS tools both off the shelf and homebrewed. I’ve been deploying and building plenty of WordPress sites and plugins, as well as extensions for full-service CMS sites in .NET. But for me, everything changed when I first heard of headless,…

Why Use Flutter to Develop Mobile Apps?

Mobile application development continues to evolve and surprise the world with its innovation every day.  As digitization has taken over the avenues of education, entertainment, and commerce, it has directly led to an huge surge in the need for mobile apps. They are an essential part of our daily lives. They help us communicate, meditate,…

Are Google’s Core Web Vitals Metrics Unreasonable?

Google’s influence over web designers, website owners, and everyday users is undeniable. The company is hyper-aggressive in detailing what it wants to see from the first two groups in order to serve up relevant results to the third. One of their more recent efforts in this area revolves around something called “Core Web Vitals”. These…