Tuesday, 30 November 2021

Diagonal Stripes Wipe Animation

I was playing this game on Apple Arcade the other day called wurdweb. It’s a fun little game! Little touches like the little shape dudes that walk around the screen (but otherwise don’t do anything) give it a lot of character. I kinda want little shape dudes that walk around on websites. But another UI choice caught my eye, the way that transitions between screens have these diagonal lines that grow and fill the screen, like window blinds closing, kinda.

Here’s a quick screencast showing how those wipes work:

I wanted to have a crack at building this.

The first thing that went through my mind is repeating-linear-gradient and how that can be used to build stripes. So say we set up like this:

.gradient {
  background-image:
    repeating-linear-gradient(
      45deg,
      #ff8a00,
      #ff8a00 10px,
      #e52e71 10px,
      #e52e71 20px
    );
}

That would buy us stripes like this:

We can use transparent as a color though. Meaning if we covered the screen with stripes like these, we could see through where that color is. Say like this:

In that gradient definition, we use 10px as the “start” and 20px as the “end” of the gradient before it repeats. Part of the trick here is keeping that 20px “end” the same and animating the “start” number up to it. When we do that, it actually covers the screen in a solid color. The problem is… how do you animate it? You can’t do this:

Screenshot of a CSS code snippet on a dark gray background with syntax highlighting. An arrow is pointing from the repeating linear gradient on the element to another repeating linear gradient inside keyframes. A note that says not going to animate is displayed in large white letters above a crying emoji.

What we need to do is animate that “start” pixel value number alone. We can use a custom property, but it’s a little tricky because without declaring them, custom properties are just strings, and not animatable lengths. So we’d have to do it like this.

@property --start {
  syntax: "<length>";
  inherits: false;
  initial-value: 10px;
}
#cover {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-image: repeating-linear-gradient(
    45deg,
    #ff8a00,
    #ff8a00 var(--start),
    transparent var(--start),
    transparent var(--end, 20px)
  );
  animation: cover 1s linear infinite;
}
@keyframes cover {
  to {
    --start: 20px;
  }
}

We’ve got to use @property here to do this, which I really like but, sadly, has limited browser support. It does work though! I’ve got all that set up, including a quick prefers-reduced-motion media query. I’m using a smidge of JavaScript to change the background halfway through the animation (while the screen is covered) so you can see how it might be used for a screen transition. Again, note that this is only working in Chromium-based browsers at the moment:

Notice I’ve used CSS custom properties for other things as well, like the angle and size of the stripes and the speed of the animation. They are both very trivial to change! I’ve chucked in knobs so you can adjust things to your liking. Knobs? Yeah, they are cool:

Like and subscribe

This whole thing started as a tweet. In this case, I’m glad I did as Temani Afif chimed in with a way to do it with masks as well, meaning pretty solid support across all browsers:

I don’t think animating background color stops or a mask position is particularly performant, but since we’re talking “screen wipes” here, one could imagine that the page isn’t likely to be interacted with anymore until the page transition is over, so maybe that’s not the world’s biggest deal.


The post Diagonal Stripes Wipe Animation appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3D3XOhn
via IFTTT

A Look at the Cloudinary WordPress Plugin

(This is a sponsored post.)

Cloudinary (the media hosting and optimization service) has a brand new version (v3) of its WordPress plugin that has really nailed it. First, a high-level look at the biggest things this plugin does:

  • It takes over your media handling. Images and video are served by Cloudinary instead of your own server, which is good for a whole host of reasons. But don’t worry, your assets are also on your own server, so there is no lock-in.
  • It serves your images and video as performantly as possible. Everything is optimized, served in the best format, and use techniques like responsive images and lazy loading, all while providing a good loading experience. All together, those things are massive for performance.
  • It provides and image gallery block with lots of functionality.

Setting it up is as easy as copy and pasting from your Cloudinary account.

So, yes, you need a Cloudinary account. You can check out the programmable media plans here. There is a free tier that will likely work for many sites and paid plans that will cover sites that have heavy media needs, of which you’ll likely find the pricing amicable. Once you have your account, you pop the connection string (from your dashboard) into this quick onboarding wizard and you’re basically done. The default settings are good.

You could do literally nothing else and the plugin will work its magic, but it’s fun to look through all the settings.

Here are the general settings:

Those two (default) settings are important. Auto sync is nice in that all your images (even your entire existing media library) is synced up to Cloudinary and stays in sync. This is necessary to host your images (and do fancy optional stuff like “transforms”) but you could otherwise think of it as a backup. When you use “Cloudinary and WordPress” as the Storage setting, it means that media will be uploaded to your own server and Cloudinary. That’s what I would highly recommend, but if you’re in a situation where, say, you have very limited or no storage on your WordPress host, you could have the images go straight to Cloudinary (only).

In the Image settings, you can see two of Cloudinary’s most powerful weapons: f_auto and q_auto, standing for “auto image formatting” and “auto quality compression.” Those are defaults I’d highly recommend leaving alone. It means that any browser on any device gets the best possible format of the image, and Cloudinary adjusts the quality as appropriate for that image. Cloudinary has very good tech for doing this, so let it do it.

The “doing images right” checklist is a thing.

Remember, we blogged it just recently. Host them on a CDN. Optimze them. Serve them in the best possible format for the requesting browser. Use responsive images. Lazy load them. None of those things are trivial, and that’s just a partial list. The good news is: this plugin does all that stuff for you, does it well, and does it without you having to think too much about it.

Showing the source HTML code for an image block. It's a lot of code, starting with the image tag and all of the srcset attributes to make the image responsive.

I like seeing the output. This is where the rubber meets the road. From this I can see that responsive images are implemented correctly and lots of different sizes are available. I can see the the image sources are pointing at the Cloudinary CDN. I can see lazy loading is implemented and working. I can see the width and height attributes are there as they should be to ensure space is reserved for the images during loading. This is everything.

It goes the extra mile by hosting the images the used by the theme as well.

Heck, it replaces CSS background-images in your theme’s stylesheet with Cloudinary-hosted versions. That’s… amazing. There must be some real clever WordPress filter stuff going on.

I like seeing this in there:

Screenshot of a Cloudinary screen in the WordPress admin. It provides settings for the Gallery Block, including colors, main viewer options, carousel, and a toggle for advanced settings. A preview of the block is to the right of the settings.

Why? It shows that this plugin is part of modern WordPress. Block editor WordPress. The block itself is simple, but useful. It shows images in a variety of useful layouts with a “lightbox”-like effect (wow, it’s been a long time since I’ve typed the word lightbox). Hey, sometimes you just need a dang image gallery and you might as well use one that is well done.

Who am I to say?

Just a lowly blogger, I suppose. But I can tell you I’ve been watching this evolve for quite a while. A ways back, I had implemented a hand-rolled Cloudinary integration here on CSS-Tricks because I wanted all this stuff. I ultimately had to give up on it as it was more technical debt than I could maintain.

The previous versions of the WordPress plugin were better, but it’s not until now, v3, where this integration is truly nailed.

Shortly after that time I tore down my custom integration, I blogged “Workflow Considerations for Using an Image Management Service” and outlined what I thought the (rather high) bar would be for integrating a third-party image host. It was a lot to ask, and I wasn’t really sure if anyone would find the incentive and motivation to do it all. Well, Cloudinary has done it here. This is as perfect a media management plugin as I could imagine.


The post A Look at the Cloudinary WordPress Plugin appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3o7rvKa
via IFTTT

Using the Reflog to Restore Lost Commits

This article is part of our “Advanced Git” series. Be sure to follow us on Twitter or sign up for our newsletter to hear about future articles!

The “Reflog” is one of Git’s lesser-known features—but one that can be extremely helpful. Some people refer to it as a “safety net,” while I like to think of it as Git’s “diary.” That’s because Git uses it to keep a journal about every movement of the HEAD pointer (i.e. every time you commit, merge, rebase, cherry-pick, reset, etc.). Git logs your actions in the Reflog which makes it a valuable logbook and a good starting point when something went wrong.

In this last part of our “Advanced Git” series, I’ll explain the differences between git log and git reflog, and I’ll show you how to use the Reflog to recover deleted commits as well as deleted branches.

git log or git reflog: what’s the difference?

In previous articles, I’ve recommended you use the git log command to inspect previous events and look at your commit history, and that’s exactly what it does. It shows the current HEAD and its ancestors, i.e. its parent, the next parent in line, etc. The log goes all the way back in the commit history by recursively printing every commit’s parent. It’s part of the repository which means it gets replicated after you push, fetch, or pull.

git reflog, on the other hand, is a private and workspace-related recording. It doesn’t go through the list of ancestors. Instead, it shows an ordered list of all commits which HEAD has pointed to in the past. That’s why you can think of it as some kind of “undo history” like you might see in word processors, text editors, etc.

This local recording technically isn’t part of the repository and it’s stored separately from the commits. The Reflog is a file in .git/logs/refs/heads/ and it tracks the local commits for every branch. Git’s diary usually gets cleaned up after 90 days (that’s the default setting), but you can easily adjust the expiration date of the Reflog. To change the number of days to 180, simply type the following command:

$ git config gc.reflogExpire 180.days.ago
Screenshot of a Terminal with a light yellow background and black text. The content shows the output from the git config gc.reflogExpire 180.days.ago command.
The repository’s configuration file (.git/config) now includes the variable reflogExpire with the value 180.days.ago

Alternatively, you can decide that your Reflog should never expire:

$ git config gc.reflogExpire never

Tip: Remember that Git makes a distinction between the repository’s configuration file (.git/config), the global per-user configuration ($HOME/.gitconfig), and the system-wide settings (/etc/gitconfig). To adjust the Reflog’s expiration date for the user or the system, add the --system or --global parameter to the commands shown above.

Enough theoretical background—let me show you how to work with git reflog to correct mistakes.

Recovering deleted commits

Imagine the following scenario: After looking at your commit history, you decide to get rid of the last two commits. You courageously perform a git reset, the two commits disappear from the commit history… and a while later, you notice that this was a mistake. You’ve just lost valuable changes and start to panic!

Do you really have to start from scratch again? You don’t. In other words: keep calm and use git reflog!

So, let’s mess things up and make this mistake in real life. The next image shows our original commit history in Tower, a graphical Git client:

Screenshot of the Tower application interface. On the left is a navigation with the History item selected. In the center panel is a visual outline of the commit history showing the avatars of people who made the commits. The second commit is selected and on the right is a panel showing more detail about the commit, including the author, date, committer, refs, hashes, and changed files.

We want to get rid of two commits and make the “Change headlines for about and imprint” commit (ID: 2b504bee) our last revision on the master branch. All we need to do is copy the hash ID to the clipboard and then use git reset on the command line and enter that hash:

$ git reset --hard 2b504bee
Screenshot of the Tower application interface. The master branch is selected in the right navigation, the first commit is selected in the center panel, and the detail for that commit is displayed in the right panel. The commits in the center panel are clean and linear without any additional commits or branches.

VoilĂ . The commits have disappeared. Now, let’s assume this was a mistake and take a look at the Reflog to recover the lost data. Type git reflog to view the journal in your terminal:

Screenshot of an open Terminal window with a light yellow background. The text is mainly black, but some words are highlighted in red, light blue and bright green. The top line is the git reset --hard 2b504bee command. The second line says the head is now at that commit ID. The third line is the git reflog command, which outputs the history.

You’ll notice that all entries are ordered chronologically. That means: the most recent—the newest—commits are at the top. And, if you look closely, you will notice the fatal git reset action from a few minutes ago right at the top.

The journal seems to work—that’s good news. So, let’s use it to undo that last action and restore the state before the reset command. Copy the hash ID (which is e5b19e4 in this specific example) to the clipboard, like before. You could use git reset again, which is totally valid. But in this case, I’m going to create a new branch based on the old state:

$ git branch happy-ending e5b19e4

Let’s take a look at our graphical Git client again:

A screenshot of the terminal with a light yellow background and the output for git reflog in it, on top of a screenshot of the Tower application window, showing the updated commit history following the command.

As you can see, the new branch, happy-ending, has been created and it includes the commits we deleted earlier—awesome, nothing is lost!

Let’s look at another example and use the Reflog to recover an entire branch.

Recovering deleted branches

The next example resembles our first scenario: we’re going to delete something—this time, it’s an entire branch that has to go. Maybe your customer or your team leader has told you to get rid of a feature branch, maybe it was your own idea to clean up. To make things worse, a commit (C3 in the picture) is not included in any of the other branches, so you’re definitely going to lose data:

Illustration showing the commit history flow of a feature/login branch with ID C2 being deleted from a C2 branch that is off the master branch. Beside the diagram is a list of the steps taken to deleted the branch, ending with step 4: you panic next to a screaming emoji.

Let’s actually do this and then recover the branch later:

Screenshot of the Tower app interface showing the login branch selected in the left panel, the commit history of that branch in the center panel with the first commit selected and highlighted in blue, then details for the commit in the right panel, including the author, date, refs, cases, and modified files.

Before you can delete the branch feature/login, you need to step away from it. (As you can see in the screenshot, it’s the current HEAD branch, and you can’t delete the HEAD branch in Git.) So, we’re going to switch branches (to master) and then we’re going to delete feature/login:

Screenshot of an open Terminal window with a light yellow background and mostly black text, though the branch and committer names are highlighted in bright green. The first command is git status, the second is git checkout master, the third is git branch -vv, the fourth is git branch -D feature/login, and the last command is git branch -vv.

Okay… now let’s say our customer or team lead had a change of heart. The feature/login branch (including its commits) is wanted after all. What should we do?

Let’s take a look at Git’s diary:

$ git reflog
776f8ca (HEAD -> master) HEAD@{0}: checkout: moving from feature/login to master
b1c249b (feature/login) HEAD@{1}: checkout: moving from master to feature/login
[...]

Turns out we’re lucky again. The last entry shows our switch from feature/login to master. Let’s try to return to the state right before that and copy the hash ID b1c249b to the clipboard. Next, we’re going to create a branch called feature/login based on the desired state:

$ git branch feature/login b1c249b
$ git branch -vv
  feature/login b1c249b Change Imprint page title
* master        776f8ca Change about title and delete error page

Great—the branch is back from the dead and also includes that valuable commit we thought we had lost:

Screenshot of the Tower application interface. The feature/login branch is selected in the left panel, the commit history for the branch is in the center panel with the first commit selected, and the left panel displays more information ab out the commit, including the author, date, refs, hashes, and modified files.

If you’re using Git in a desktop GUI like Tower, you can simply press CMD+Z to undo your last action, just like a text editor or word processor when you make a typo.

Keep calm and keep track

Git’s Reflog can be a real lifesaver! As you can see, it’s quite easy to bring lost commits or even entire branches out from the grave. What you need to do is find the correct hash ID in the Reflog—the rest is a piece of cake.

If you want to dive deeper into advanced Git tools, feel free to check out my (free!) “Advanced Git Kit”: it’s a collection of short videos about topics like branching strategies, Interactive Rebase, Reflog, Submodules and much more.

This was the last part in our series on “Advanced Git” here at CSS-Tricks. I hope you enjoyed the articles. Happy hacking!


The post Using the Reflog to Restore Lost Commits appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3rmClOt
via IFTTT

Monday, 29 November 2021

Recreating the Apple Music Hits Playlist Animation in CSS

Apple Music has this “Spatial Audio” feature where the direction of the music in your headphones is based on the location of the device. It’s tough to explain just how neat it is. But that’s not what I’m here to talk about.

I opened up the Apple Music app and saw a featured playlist of hit songs that support Spatial Audio. The cover for it is this brightly-colored pink container that holds a bunch of boxes stacked one on top of another. The boxes animate in one at a time, fading in at the center of the container, then fading out as it scales to the size of the container. Like an infinite loop.

Animated GIF showing the Apple Music UI we are recreating. It's brightly colored shades of pink against a dark gray background with information about the playlist to the right of the pattern, and options to play and shuffle the sings in orange buttons.

Cool! I knew I had to re-create it in CSS. So I did.

Here’s how it works…

The markup

I started with the HTML. There’s obviously a container we need to define, plus however many boxes we want to animate. I went with an even 10 boxes in the container.

<div class="container">
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <!-- etc. -->
</div>

That’s literally it for HTML. We are free to jump right into the CSS!

Styling the container

Nothing too fancy here. I measured approximate dimensions based on what I saw in Apple Music, which happened to be 315px × 385px. then I took a screenshot of the cover and dropped it into my image editing app to get the lightest possible color, which is around the outside edges of the container. My color picker landed on #eb5bec.

.container {
  background-color: #eb5bec;
  height: 315px;
  width: 385px;
}

As I was doing this, I knew I would probably want this to be a grid container to align the boxes and any other elements in the center. I also figured that the boxes themselves would start from the center of the container and stack on top of one another, meaning there will be some absolute positioning. That also means the container ought to have relative positioning to reign them in.

.container {
  background-color: #eb5bec;
  height: 315px;
  position: relative;
  width: 385px;
}

And since we want the boxes to start from the center, we can reach for grid to help with that:

.container {
  background-color: #eb5bec;
  display: grid;
  height: 315px;
  place-items: center;
  position: relative;
  width: 385px;
}

If the boxes in the container are growing outward, then there’s a chance that they could expand beyond the container. Better hide any possible overflow.

.container {
  background-color: #eb5bec;
  height: 315px;
  overflow: hidden;
  position: relative;
  width: 385px;
}

I also noticed some rounded corners on it, so let’s drop that in while we’re here.

.container {
  background-color: #eb5bec;
  border-radius: 16px;
  height: 315px;
  position: relative;
  width: 385px;
}

So far, so good!

Styling the boxes

We have 10 .box elements in the markup and we want them stacked on top of one another. I started with some absolute positioning, then sized them at 100px square. Then I did the same thing with my image editing app to find the darkest color value of a box, which was #471e45.

.box {
  background: #471e45;
  height: 100px;
  position: absolute;
  width: 100px;
}

The boxes seem to fade out as they grow. That allows one box to be seen through the other, so let’s make them opaque to start.

.box {
  background: #471e45;
  height: 100px;
  opacity: 0.5;
  position: absolute;
  width: 100px;
}

Cool, cool. We’re unable to see all the boxes as they’re stacked on top of one another, but we’re making progress!

Creating the animation

Ready to write some @keyframes? We’re gonna make this super simple, going from 0 to 100% without any steps in between. We don’t even need those percentages!

@keyframes grow {
  from {
    /* do stuff */
  }
  to {
    /* do stuff */
  }
}

Specifically, we want two things to happen from start to finish:

  • The boxes go from our starting opacity value of 0.5 to 0 (fully transparent).
  • The boxes scale up to the edges of the container.
@keyframes grow {
  from {
    opacity: 0.5;
    transform: scale(0);
  }
  to {
    opacity: 0;
    transform: scale(3.85);
  }
}

How’d I land on scaling the boxes up by 3.85? Our boxes are 100px square and the container is 385px tall. A value of 3.85 gets the boxes up to 385px as they fade completely out which makes for a nice linear animation when we get there.

Speaking of which…

Applying the animation

It’s pretty easy to call the animation on our boxes. Just gotta make sure it moves in a liner timing function on an infinite basis so it’s like the Energizer Bunny and keeps going and going and going and going and…

.box {
  animation: grow 10s linear infinite; /* 10s = 10 boxes */
  /* etc. */
}

This gives us the animation we want. But! The boxes are all moving at the same time, so all we see is one giant box growing.

We’ve gotta stagger those little fellers. No loops in vanilla CSS, unfortunately, so we have to delay each box individually. We can start by setting a custom property for the delay, set it to one second, then redefine the custom property on each instance.

.box {
  --delay: 1s;
  
  animation-delay: var(--delay);
  /* same as before */
}
.box:nth-child(2) {
  --delay: 2s;
}
.box:nth-child(3) {
  --delay: 3s;
}
.box:nth-child(4) {
  --delay: 4s;
}
.box:nth-child(5) {
  --delay: 5s;
}
/* five more times... */

Huzzah!

Keep on rockin’

That’s it! We just recreated the same sort of effect used by Apple Music. There are a few finishing touches we could plop in there, like the content and whatnot. Here’s my final version again:


The post Recreating the Apple Music Hits Playlist Animation in CSS appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/31dzmNy
via IFTTT

We Analyzed 425,909 Favicons

This is a neat idea for a research project. The big map is fun, but the research had some tidbits in it worth looking at.

The average favicon network request takes 130ms, at least from our speedy cloud instance.

Fast, but not that fast, particularly for a file that nearly every website in the world has. All the more reason to get it right and ensure only one is downloaded (ideally SVG).

I would have guessed most favicons are ICO, but no:

The vast majority of the favicons offered up by websites are PNG. 71.6% of <link rel=”icon”> images are PNG.

And lol:

21.1% of /favicon.ico files are secretly PNGs

One of the reasons that file extensions, to browsers, are rather meaningless. It’s all about that content-type.

Also, they question the accuracy of the method, but the dominant color in the analysis so far is purple. I hope it ends up true as it kinda makes sense. Unless you’re offering different favicons for dark mode, using white or black seems too dangerous these days.

To Shared LinkPermalink on CSS-Tricks


The post We Analyzed 425,909 Favicons appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3D27Uzh
via IFTTT

Friday, 26 November 2021

When is it “Right” to Reach for contain and will-change in CSS?

I’ve got some blind spots in CSS-related performance things. One example is the will-change property. It’s a good name. You’re telling the browser some particular property (or the scroll-position or content) uh, will, change:

.el {
  will-change: opacity;
}
.el.additional-hard-to-know-state {
  opacity: 0;
}

But is that important to do? I don’t know. The point, as I understand it, is that it will kick .el into processing/rendering/painting on the GPU rather than CPU, which is a speed boost. Sort of like the classic transform: translate3d(0, 0, 0); hack. In the exact case above, it doesn’t seem to my brain like it would matter. I have in my head that opacity is one of the “cheapest” things to animate, so there is no particular benefit to will-change. Or maybe it matters noticeably on some browsers or devices, but not others? This is front-end development after all.

There was a spurt of articles about will-change around 2014/2015 that warn about weird behavior, like unexpected changes in stacking contexts and being careful not to use it “too much.” There was also advice spreading around that you should never use this property directly in CSS stylesheets; you should only apply it in JavaScript before the state change, then remove it after you no longer need it.

I have no idea if any of those things are still true. Sorry! I’d love to read a 2022 deep dive on will-change. We’re capable of that kind of testing, so I’ll put it in the idea pile. But my point is that there are things in CSS that are designed explicitly for performance that are confusing to me, and I wish I had a more full understanding of them because they seem like Very Big Deals.

Take “How I made Google’s data grid scroll 10x faster with one line of CSS” by Johan Isaksson. A 10✕ scrolling performance improvement is a massive deal! Know how they fixed it?

[…] as I was browsing the “Top linking sites” page I noticed major scroll lag. This happens when choosing to display a larger dataset (500 rows) instead of the default 10 results.

[…]

So, what did I do? I simply added a single line of CSS to the <table> on the Elements panel, specifying that it will not affect the layout or style of other elements on the page

table {
  contain: strict; 
}

The contain property is another that I sort of get, but I’d still call it a blind spot because my brain doesn’t just automatically think of when I could (or should?) use it. But that’s a bummer, because clearly I’m not building interfaces as performant as I could be if I did understand contain better.

There’s another! The content-visibility property. The closest I came to understanding it was after watching Jake and Surma’s video on it where they used it (along with contain-intrinsic-size and some odd magic numbers) to dramatically speed up a long page. What hasn’t stuck with me is when I should use it on my pages.

Are all three of these features “there if you need them” features? Is it OK to ignore them until you notice poor performance on something (like a massive page) and then reach for them to attempt to solve it? Almost “don’t use these until you need them,” otherwise you’re in premature optimization territory. The trouble with that is the classic situation where you won’t actually notice the poor performance unless you are very actively testing on the lowest-specced devices out there.

Or are these features “this is what modern CSS is and you should be thinking of them like you think of padding” territory? I kind of suspect it’s more like that. If you’re building an element you know won’t change in certain ways, it’s probably worth “containing” it. If you’re building an element you know will change in certain ways, it’s probably worth providing that info to browsers. If you’re building a part of page you know is always below the fold, it’s probably worth avoiding the paint on it. But personally, I just don’t have enough of this fully grokked to offer any solid advice.


The post When is it “Right” to Reach for contain and will-change in CSS? appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/2ZqkOJJ
via IFTTT

A Handy Little System for Animated Entrances in CSS

I love little touches that make a website feel like more than just a static document. What if web content wouldn’t just “appear” when a page loaded, but instead popped, slid, faded, or spun into place? It might be a stretch to say that movements like this are always useful, though in some cases they can draw attention to certain elements, reinforce which elements are distinct from one another, or even indicate a changed state. So, they’re not totally useless, either.

So, I put together a set of CSS utilities for animating elements as they enter into view. And, yes, this pure CSS. It not only has a nice variety of animations and variations, but supports staggering those animations as well, almost like a way of creating scenes.

You know, stuff like this:

Which is really just a fancier version of this:

We’ll go over the foundation I used to create the animations first, then get into the little flourishes I added, how to stagger animations, then how to apply them to HTML elements before we also take a look at how to do all of this while respecting a user’s reduced motion preferences.

The basics

The core idea involves adding a simple CSS @keyframes animation that’s applied to anything we want to animate on page load. Let’s make it so that an element fades in, going from opacity: 0 to opacity: 1 in a half second:

.animate {
  animation-duration: 0.5s;
  animation-name: animate-fade;
  animation-delay: 0.5s;
  animation-fill-mode: backwards;
}

@keyframes animate-fade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

Notice, too, that there’s an animation-delay of a half second in there, allowing the rest of the site a little time to load first. The animation-fill-mode: backwards is there to make sure that our initial animation state is active on page load. Without this, our animated element pops into view before we want it to.

If we’re lazy, we can call it a day and just go with this. But, CSS-Tricks readers aren’t lazy, of course, so let’s look at how we can make this sort of thing even better with a system.

Fancier animations

It’s much more fun to have a variety of animations to work with than just one or two. We don’t even need to create a bunch of new @keyframes to make more animations. It’s simple enough to create new classes where all we change is which frames the animation uses while keeping all the timing the same.

There’s nearly an infinite number of CSS animations out there. (See animate.style for a huge collection.) CSS filters, like blur(), brightness() and saturate() and of course CSS transforms can also be used to create even more variations.

But for now, let’s start with a new animation class that uses a CSS transform to make an element “pop” into place.

.animate.pop {
  animation-duration: 0.5s;
  animation-name: animate-pop;
  animation-timing-function: cubic-bezier(.26, .53, .74, 1.48);
}

@keyframes animate-pop {
  0% {
    opacity: 0;
    transform: scale(0.5, 0.5);
  }

  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

I threw in a little cubic-bezier() timing curve, courtesy of Lea Verou’s indispensable cubic-bezier.com for a springy bounce.

Adding delays

We can do better! For example, we can animate elements so that they enter at different times. This creates a stagger that makes for complex-looking motion without a complex amount of code.

This animation on three page elements using a CSS filter, CSS transform, and staggered by about a tenth of a second each, feels really nice:

All we did there was create a new class for each element that spaces when the elements start animating, using animation-delay values that are just a tenth of a second apart.

.delay-1 { animation-delay: 0.6s; }  
.delay-2 { animation-delay: 0.7s; }
.delay-3 { animation-delay: 0.8s; }

Everything else is exactly the same. And remember that our base delay is 0.5s, so these helper classes count up from there.

Respecting accessibility preferences

Let’s be good web citizens and remove our animations for users who have enabled their reduced motion preference setting:

@media screen and (prefers-reduced-motion: reduce) {
  .animate { animation: none !important; }
}

This way, the animation never loads and elements enter into view like normal. It’s here, though, that is worth a reminder that “reduced” motion doesn’t always mean “remove” motion.

Applying animations to HTML elements

So far, we’ve looked at a base animation as well as a slightly fancier one that we were able to make even fancier with staggered animation delays that are contained in new classes. We also saw how we can respect user motion preferences at the same time.

Even though there are live demos that show off the concepts, we haven’t actually walked though how to apply our work to HTML. And what’s cool is that we can use this on just about any element, whether its a div, span, article, header, section, table, form… you get the idea.

Here’s what we’re going to do. We want to use our animation system on three HTML elements where each element gets three classes. We could hard-code all the animation code to the element itself, but splitting it up gives us a little animation system we can reuse.

  • .animate: This is the base class that contains our core animation declaration and timing.
  • The animation type: We’ll use our “pop” animation from before, but we could use the one that fades in as well. This class is technically optional but is a good way to apply distinct movements.
  • .delay-<number>: As we saw earlier, we can create distinct classes that are used to stagger when the animation starts on each element, making for a neat effect. This class is also optional.

So our animated elements might now look like:

<h2 class="animate pop">One!</h2>
<h2 class="animate pop delay-1">Two!</h2>
<h2 class="animate pop delay-2">Three!</h2>

Let’s count them in!

Conclusion

Check that out: we went from a seemingly basic set of @keyframes and turned it into a full-fledged system for applying interesting animations for elements entering into view.

This is ridiculously fun, of course. But the big takeaway for me is how the examples we looked at form a complete system that can be used to create a baseline, different types of animations, staggered delays, and an approach for respecting user motion preferences. These, to me, are all the ingredients for a flexible system that easy to use, while giving us a lot with a little and without a bunch of extra cruft.

What we covered could indeed be a full animation library. But, of course, I did’t stop there and have my entire CSS file of animations in all its glory for you. There are several more types of animations in there, including 15 classes of different delays that can be used for staggering things. I’ve been using these on my own projects, but it’s still an early draft and I love feedback on it—so please enjoy and let me know what you think in the comments!

/* ==========================================================================
Animation System by Neale Van Fleet from Rogue Amoeba
========================================================================== */
.animate {
  animation-duration: 0.75s;
  animation-delay: 0.5s;
  animation-name: animate-fade;
  animation-timing-function: cubic-bezier(.26, .53, .74, 1.48);
  animation-fill-mode: backwards;
}

/* Fade In */
.animate.fade {
  animation-name: animate-fade;
  animation-timing-function: ease;
}

@keyframes animate-fade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

/* Pop In */
.animate.pop { animation-name: animate-pop; }

@keyframes animate-pop {
  0% {
    opacity: 0;
    transform: scale(0.5, 0.5);
  }
  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

/* Blur In */
.animate.blur {
  animation-name: animate-blur;
  animation-timing-function: ease;
}

@keyframes animate-blur {
  0% {
    opacity: 0;
    filter: blur(15px);
  }
  100% {
    opacity: 1;
    filter: blur(0px);
  }
}

/* Glow In */
.animate.glow {
  animation-name: animate-glow;
  animation-timing-function: ease;
}

@keyframes animate-glow {
  0% {
    opacity: 0;
    filter: brightness(3) saturate(3);
    transform: scale(0.8, 0.8);
  }
  100% {
    opacity: 1;
    filter: brightness(1) saturate(1);
    transform: scale(1, 1);
  }
}

/* Grow In */
.animate.grow { animation-name: animate-grow; }

@keyframes animate-grow {
  0% {
    opacity: 0;
    transform: scale(1, 0);
    visibility: hidden;
  }
  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

/* Splat In */
.animate.splat { animation-name: animate-splat; }

@keyframes animate-splat {
  0% {
    opacity: 0;
    transform: scale(0, 0) rotate(20deg) translate(0, -30px);
    }
  70% {
    opacity: 1;
    transform: scale(1.1, 1.1) rotate(15deg));
  }
  85% {
    opacity: 1;
    transform: scale(1.1, 1.1) rotate(15deg) translate(0, -10px);
  }

  100% {
    opacity: 1;
    transform: scale(1, 1) rotate(0) translate(0, 0);
  }
}

/* Roll In */
.animate.roll { animation-name: animate-roll; }

@keyframes animate-roll {
  0% {
    opacity: 0;
    transform: scale(0, 0) rotate(360deg);
  }
  100% {
    opacity: 1;
    transform: scale(1, 1) rotate(0deg);
  }
}

/* Flip In */
.animate.flip {
  animation-name: animate-flip;
  transform-style: preserve-3d;
  perspective: 1000px;
}

@keyframes animate-flip {
  0% {
    opacity: 0;
    transform: rotateX(-120deg) scale(0.9, 0.9);
  }
  100% {
    opacity: 1;
    transform: rotateX(0deg) scale(1, 1);
  }
}

/* Spin In */
.animate.spin {
  animation-name: animate-spin;
  transform-style: preserve-3d;
  perspective: 1000px;
}

@keyframes animate-spin {
  0% {
    opacity: 0;
    transform: rotateY(-120deg) scale(0.9, .9);
  }
  100% {
    opacity: 1;
    transform: rotateY(0deg) scale(1, 1);
  }
}

/* Slide In */
.animate.slide { animation-name: animate-slide; }

@keyframes animate-slide {
  0% {
    opacity: 0;
    transform: translate(0, 20px);
  }
  100% {
    opacity: 1;
    transform: translate(0, 0);
  }
}

/* Drop In */
.animate.drop { 
  animation-name: animate-drop; 
  animation-timing-function: cubic-bezier(.77, .14, .91, 1.25);
}

@keyframes animate-drop {
0% {
  opacity: 0;
  transform: translate(0,-300px) scale(0.9, 1.1);
}
95% {
  opacity: 1;
  transform: translate(0, 0) scale(0.9, 1.1);
}
96% {
  opacity: 1;
  transform: translate(10px, 0) scale(1.2, 0.9);
}
97% {
  opacity: 1;
  transform: translate(-10px, 0) scale(1.2, 0.9);
}
98% {
  opacity: 1;
  transform: translate(5px, 0) scale(1.1, 0.9);
}
99% {
  opacity: 1;
  transform: translate(-5px, 0) scale(1.1, 0.9);
}
100% {
  opacity: 1;
  transform: translate(0, 0) scale(1, 1);
  }
}

/* Animation Delays */
.delay-1 {
  animation-delay: 0.6s;
}
.delay-2 {
  animation-delay: 0.7s;
}
.delay-3 {
  animation-delay: 0.8s;
}
.delay-4 {
  animation-delay: 0.9s;
}
.delay-5 {
  animation-delay: 1s;
}
.delay-6 {
  animation-delay: 1.1s;
}
.delay-7 {
  animation-delay: 1.2s;
}
.delay-8 {
  animation-delay: 1.3s;
}
.delay-9 {
  animation-delay: 1.4s;
}
.delay-10 {
  animation-delay: 1.5s;
}
.delay-11 {
  animation-delay: 1.6s;
}
.delay-12 {
  animation-delay: 1.7s;
}
.delay-13 {
  animation-delay: 1.8s;
}
.delay-14 {
  animation-delay: 1.9s;
}
.delay-15 {
  animation-delay: 2s;
}

@media screen and (prefers-reduced-motion: reduce) {
  .animate {
    animation: none !important;
  }
}

The post A Handy Little System for Animated Entrances in CSS appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3xqtQmA
via IFTTT

Wednesday, 24 November 2021

Creating Generative Patterns with The CSS Paint API

The browser has long been a medium for art and design. From Lynn Fisher’s joyful A Single Div creations to Diana Smith’s staggeringly detailed CSS paintings, wildly creative, highly skilled developers have — over the years — continuously pushed web technologies to their limits and crafted innovative, inspiring visuals.

CSS, however, has never really had an API dedicated to… well, just drawing stuff! As demonstrated by the talented folks above, it certainly can render most things, but it’s not always easy, and it’s not always practical for production sites/applications.

Recently, though, CSS was gifted an exciting new set of APIs known as Houdini, and one of them — the Paint API — is specifically designed for rendering 2D graphics. For us web folk, this is incredibly exciting. For the first time, we have a section of CSS that exists for the sole purpose of programmatically creating images. The doors to a mystical new world are well and truly open!

In this tutorial, we will be using the Paint API to create three (hopefully!) beautiful, generative patterns that could be used to add a delicious spoonful of character to a range of websites/applications.

Spellbooks/text editors at the ready, friends, let’s do some magic!

Intended audience

This tutorial is perfect for folks who are comfortable writing HTML, CSS, and JavaScript. A little familiarity with generative art and some knowledge of the Paint API/HTML canvas will be handy but not essential. We will do a quick overview before we get started. Speaking of which…

Before we start

For a comprehensive introduction to both the Paint API and generative art/design, I recommend popping over to the first entry in this series. If you are new to either subject, this will be a great place to start. If you don’t feel like navigating another article, however, here are a couple of key concepts to be familiar with before moving on.

If you are already familiar with the CSS Paint API and generative art/design, feel free to skip ahead to the next section.

What is generative art/design?

Generative art/design is any work created with an element of chance. We define some rules and allow a source of randomness to guide us to an outcome. For example, a rule could be “if a random number is greater than 50, render a red square, if it is less than 50, render a blue square*,”* and, in the browser, a source of randomness could be Math.random().

By taking a generative approach to creating patterns, we can generate near-infinite variations of a single idea — this is both an inspiring addition to the creative process and a fantastic opportunity to delight our users. Instead of showing people the same imagery every time they visit a page, we can display something special and unique for them!

What is the CSS Paint API?

The Paint API gives us low-level access to CSS rendering. Through “paint worklets” (JavaScript classes with a special paint() function), it allows us to dynamically create images using a syntax almost identical to HTML canvas. Worklets can render an image wherever CSS expects one. For example:

.worklet-canvas {
  background-image: paint(workletName);
}

Paint API worklets are fast, responsive, and play ever so well with existing CSS-based design systems. In short, they are the coolest thing ever. The only thing they are lacking right now is widespread browser support. Here’s a table:

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Firefox IE Edge Safari
65 No No 79 No

Mobile / Tablet

Android Chrome Android Firefox Android iOS Safari
95 No 95 No

A little thin on the ground! That’s OK, though. As the Paint API is almost inherently decorative, we can use it as a progressive enhancement if it’s available and provide a simple, dependable fallback if not.

What we are making

In this tutorial, we will be learning how to create three unique generative patterns. These patterns are quite simple, but will act as a wonderful springboard for further experimentation. Here they are in all their glory!

The demos in this tutorial currently only work in Chrome and Edge.

“Tiny Specks”

“Bauhaus”

“Voronoi Arcs”

Before moving on, take a moment to explore the examples above. Try changing the custom properties and resizing the browser window — watch how the patterns react. Can you guess how they might work without peeking at the JavaScript?

Getting set up

To save time and eliminate the need for any custom build processes, we will be working entirely in CodePen throughout this tutorial. I have even created a “starter Pen” that we can use as a base for each pattern!

I know, it’s not much to look at… yet.

In the starter Pen, we are using the JavaScript section to write the worklet itself. Then, in the HTML section, we load the JavaScript directly using an internal <script> tag. As Paint API worklets are special workers (code that runs on a separate browser thread), their origin must1 exist in a standalone .js file.

Let’s break down the key pieces of code here.

If you have written Paint API worklets before, and are familiar with CodePen, you can skip ahead to the next section.

Defining the worklet class

First things first: Let’s check out the JavaScript tab. Here we define a worklet class with a simple paint() function:

class Worklet {
  paint(ctx, geometry, props) {
    const { width, height } = geometry;
    ctx.fillStyle = "#000";
    ctx.fillRect(0, 0, width, height);
  }
}

I like to think of a worklet’s paint() function as a callback. When the worklet’s target element updates (changes dimensions, modifies custom properties), it re-runs. A worklet’s paint() function automatically has a few parameters passed when it executes. In this tutorial, we are interested in the first three:

  • ctx — a 2D drawing context very similar to that of HTML canvas
  • geometry — an object containing the width/height dimensions of the worklet’s target element
  • props — an array of CSS custom properties that we can “watch” for changes and re-render when they do. These are a great way of passing values to paint worklets.

Our starter worklet renders a black square that covers the entire width/height of its target element. We will completely rewrite this paint() function for each example, but it’s nice to have something defined to check things are working.

Registering the worklet

Once a worklet class is defined, it needs to be registered before we can use it. To do so, we call registerPaint in the worklet file itself:

if (typeof registerPaint !== "undefined") {
  registerPaint("workletName", Worklet);
}

Followed by CSS.paintWorklet.addModule() in our “main” JavaScript/HTML:

<script id="register-worklet">
  if (CSS.paintWorklet) {
    CSS.paintWorklet.addModule('https://codepen.io/georgedoescode/pen/bGrMXxm.js');
  }
</script>

We are checking registerPaint is defined before running it here, as our pen’s JavaScript will always run once on the main browser thread — registerPaint only becomes available once the JavaScript file is loaded into a worklet using CSS.paintWorklet.addModule(...).

Applying the worklet

Once registered, we can use our worklet to generate an image for any CSS property that expects one. In this tutorial, we will focus on background-image:

.worklet-canvas {
  background-image: paint(workletName);
}

Package imports

You may notice a couple of package imports dangling at the top of the starter pen’s worklet file:

import random from "https://cdn.skypack.dev/random";
import seedrandom from "https://cdn.skypack.dev/seedrandom";
Can you guess what they are?

Random number generators!

All three of the patterns we are creating in this tutorial rely heavily on randomness. Paint API worklets should, however, (almost) always be deterministic. Given the same input properties and dimensions, a worklet’s paint() function should always render the same thing.

Why?

  1. The Paint API may want to use a cached version of a worklet’s paint() output for better performance. Introducing an unpredictable element to a worklet renders this impossible!
  2. A worklet’s paint() function re-runs whenever the element it applies to changes dimensions. When coupled with “pure” randomness, this can result in significant flashes of content — a potential accessibility issue for some folks.

For us, all this renders Math.random() a little useless, as it is entirely unpredictable. As an alternative, we are pulling in random (an excellent library for working with random numbers) and seedrandom (a pseudo-random number generator to use as its base algorithm).

As a quick example, here’s a “random circles” worklet using a pseudo-random number generator:

And here’s a similar worklet using Math.random(). Warning: Resizing the element results in flashing imagery.

There’s a little resize handle in the bottom-right of both of the above patterns. Try resizing both elements. Notice the difference?

Setting up each pattern

Before beginning each of the following patterns, navigate to the starter Pen and click the “Fork” button in the footer. Forking a Pen creates a copy of the original the moment you click the button. From this point, it is yours to do whatever you like.

Once you have forked the starter Pen, there is a critical extra step to complete. The URL passed to CSS.paintWorklet.addModule must be updated to point to the new fork’s JavaScript file. To find the path for your fork’s JavaScript, take a peek at the URL shown in your browser. You want to grab your fork’s URL with all query parameters removed, and append .js — something like this:

Lovely. That’s the ticket! Once you have the URL for your JavaScript, make sure you update it here:

<script id="register-worklet">
  if (CSS.paintWorklet) {
    // ⚠️ hey friend! update the URL below each time you fork this pen! ⚠️
    CSS.paintWorklet.addModule('https://codepen.io/georgedoescode/pen/QWMVdPG.js');
  }
</script>

When working with this setup, you may occasionally need to manually refresh the Pen in order to see your changes. To do so, hit CMD/CTRL + Shift + 7.

Pattern #1 (Tiny Specks)

OK, we are ready to make our first pattern. Fork the starter Pen, update the .js file reference, and settle in for some generative fun!

As a quick reminder, here’s the finished pattern:

Updating the worklet’s name

Once again, first things first: Let’s update the starter worklet’s name and relevant references:

class TinySpecksPattern {
  // ...
}
if (typeof registerPaint !== "undefined") {
  registerPaint("tinySpecksPattern", TinySpecksPattern);
}
.worklet-canvas {
  /* ... */
  background-image: paint(tinySpecksPattern);
}

Defining the worklet’s input properties

Our “Tiny Specks” worklet will accept the following input properties:

  • --pattern-seed — a seed value for the pseudo-random number generator
  • --pattern-colors — the available colors for each speck
  • --pattern-speck-count — how many individual specks the worklet should render
  • --pattern-speck-min-size — the minimum size for each speck
  • --pattern-speck-max-size — the maximum size for each speck

As our next step, let’s define the inputProperties our worklet can receive. To do so, we can add a getter to our TinySpecksPattern class:

class TinySpecksPattern {
  static get inputProperties() {
    return [
      "--pattern-seed",
      "--pattern-colors",
      "--pattern-speck-count",
      "--pattern-speck-min-size",
      "--pattern-speck-max-size"
    ];
  }
  // ...
}

Alongside some custom property definitions in our CSS:

@property --pattern-seed {
  syntax: "<number>";
  initial-value: 1000;
  inherits: true;
}

@property --pattern-colors {
  syntax: "<color>#";
  initial-value: #161511, #dd6d45, #f2f2f2;
  inherits: true;
}

@property --pattern-speck-count {
  syntax: "<number>";
  initial-value: 3000;
  inherits: true;
}

@property --pattern-speck-min-size {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}

@property --pattern-speck-max-size {
  syntax: "<number>";
  initial-value: 3;
  inherits: true;
}

We are using the Properties and Values API here (another member of the Houdini family) to define our custom properties. Doing so affords us two valuable benefits. First, we can define sensible defaults for the input properties our worklet expects. A tasty sprinkle of developer experience! Second, by including a syntax definition for each custom property, our worklet can interpret them intelligently.

For example, we define the syntax <color># for --pattern-colors. In turn, this allows us to pass an array of comma-separated colors to the worklet in any valid CSS color format. When our worklet receives these values, they have been converted to RGB and placed in a neat little array. Without a syntax definition, worklets interpret all props as simple strings.

Like the Paint API, the Properties and Values API also has limited browser support.

The paint() function

Awesome! Here’s the fun bit. We have created our “Tiny Speck” worklet class, registered it, and defined what input properties it can expect to receive. Now, let’s make it do something!

As a first step, let’s clear out the starter Pen’s paint() function, keeping only the width and height definitions:

paint(ctx, geometry, props) {
  const { width, height } = geometry;
}

Next, let’s store our input properties in some variables:

const seed = props.get("--pattern-seed").value;
const colors = props.getAll("--pattern-colors").map((c) => c.toString());
const count = props.get("--pattern-speck-count").value;
const minSize = props.get("--pattern-speck-min-size").value;
const maxSize = props.get("--pattern-speck-max-size").value;

Next, we should initialize our pseudo-random number generator:

random.use(seedrandom(seed));

Ahhh, predictable randomness! We are re-seeding seedrandom with the same seed value every time paint() runs, resulting in a consistent stream of random numbers across renders.

Finally, let’s paint our specks!

First off, we create a for-loop that iterates count times. In every iteration of this loop, we are creating one individual speck:

for (let i = 0; i < count; i++) {
}

As the first action in our for-loop, we define an x and y position for the speck. Somewhere between 0 and the width/height of the worklet’s target element is perfect:

const x = random.float(0, width);
const y = random.float(0, height);

Next, we choose a random size (for the radius):

const radius = random.float(minSize, maxSize);

So, we have a position and a size defined for the speck. Let’s choose a random color from our colors to fill it with:

ctx.fillStyle = colors[random.int(0, colors.length - 1)];

Alright. We are all set. Let’s use ctx to render something!

The first thing we need to do at this point is save() the state of our drawing context. Why? We want to rotate each speck, but when working with a 2D drawing context like this, we cannot rotate individual items. To rotate an object, we have to spin the entire drawing space. If we don’t save() and restore() the context, the rotation/translation in every iteration will stack, leaving us with a very messy (or empty) canvas!

ctx.save();

Now that we have saved the drawing context’s state, we can translate to the speck’s center point (defined by our x/y variables) and apply a rotation. Translating to the center point of an object before rotating ensures the object rotates around its center axis:

ctx.translate(x, y);
ctx.rotate(((random.float(0, 360) * 180) / Math.PI) * 2);
ctx.translate(-x, -y);

After applying our rotation, we translate back to the top-left corner of the drawing space.

We choose a random value between 0 and 360 (degrees) here, then convert it into radians (the rotation format ctx understands).

Awesome! Finally, let’s render an ellipse — this is the shape that defines our specks:

ctx.beginPath();
ctx.ellipse(x, y, radius, radius / 2, 0, Math.PI * 2, 0);
ctx.fill();

Here’s a simple pen showing the form of our random specks, a little closer up:

Perfect. Now, all we need to do is restore the drawing context:

ctx.restore();

That’s it! Our first pattern is complete. Let’s also apply a background-color to our worklet canvas to finish off the effect:

.worklet-canvas {
  background-color: #90c3a5;
  background-image: paint(tinySpecksPattern);
}

Next steps

From here, try changing the colors, shapes, and distribution of the specks. There are hundreds of directions you could take this pattern! Here’s an example using little triangles rather than ellipses:

Onwards!

Pattern #2 (Bauhaus)

Nice work! That’s one pattern down. Onto the next one. Once again, fork the starter Pen and update the worklet’s JavaScript reference to get started.

As a quick refresher, here’s the finished pattern we are working toward:

Updating the worklet’s name

Just like we did last time, let’s kick things off by updating the worklet’s name and relevant references:

class BauhausPattern {
  // ...
}

if (typeof registerPaint !== "undefined") {
  registerPaint("bauhausPattern", BauhausPattern);
}
.worklet-canvas {
  /* ... */
  background-image: paint(bauhausPattern);
}

Lovely.

Defining the worklet’s input properties

Our “Bauhaus Pattern” worklet expects the following input properties:

  • --pattern-seed — a seed value for the pseudo-random number generator
  • --pattern-colors — the available colors for each shape in the pattern
  • --pattern-size — the value used to define both the width and height of a square pattern area
  • --pattern-detail — the number of columns/rows to divide the square pattern into

Let’s add these input properties to our worklet:

class BahausPattern {
  static get inputProperties() {
    return [
      "--pattern-seed",
      "--pattern-colors",
      "--pattern-size",
      "--pattern-detail"
    ];
  }
  // ...
}

…and define them in our CSS, again, using the Properties and Values API:

@property --pattern-seed {
  syntax: "<number>";
  initial-value: 1000;
  inherits: true;
}

@property --pattern-colors {
  syntax: "<color>#";
  initial-value: #2d58b5, #f43914, #f9c50e, #ffecdc;
  inherits: true;
}

@property --pattern-size {
  syntax: "<number>";
  initial-value: 1024;
  inherits: true;
}

@property --pattern-detail {
  syntax: "<number>";
  initial-value: 12;
  inherits: true;
}

Excellent. Let’s paint!

The paint() function

Again, let’s clear out the starter worklet’s paint function, leaving only the width and height definition:

paint(ctx, geometry, props) {
  const { width, height } = geometry;
}

Next, let’s store our input properties in some variables:

const patternSize = props.get("--pattern-size").value;
const patternDetail = props.get("--pattern-detail").value;
const seed = props.get("--pattern-seed").value;
const colors = props.getAll("--pattern-colors").map((c) => c.toString());

Now, we can seed our pseudo-random number generator just like before:

random.use(seedrandom(seed));

Awesome! As you might have noticed, the setup for Paint API worklets is always somewhat similar. It’s not the most exciting process, but it’s an excellent opportunity to reflect on the architecture of your worklet and how other developers may use it.

So, with this worklet, we create a fixed-dimension square pattern filled with shapes. This fixed-dimension pattern is then scaled up or down to cover the worklet’s target element. Think of this behavior a bit like background-size: cover in CSS!

Here’s a diagram:

To achieve this behavior in our code, let’s add a scaleContext function to our worklet class:

scaleCtx(ctx, width, height, elementWidth, elementHeight) {
  const ratio = Math.max(elementWidth / width, elementHeight / height);
  const centerShiftX = (elementWidth - width * ratio) / 2;
  const centerShiftY = (elementHeight - height * ratio) / 2;
  ctx.setTransform(ratio, 0, 0, ratio, centerShiftX, centerShiftY);
}

And call it in our paint() function:

this.scaleCtx(ctx, patternSize, patternSize, width, height);

Now, we can work to a set of fixed dimensions and have our worklet’s drawing context automatically scale everything for us — a handy function for lots of use cases.

Next up, we are going to create a 2D grid of cells. To do so, we define a cellSize variable (the size of the pattern area divided by the number of columns/rows we would like):

const cellSize = patternSize / patternDetail;

Then, we can use the cellSize variable to “step-through” the grid, creating equally-spaced, equally-sized cells to add random shapes to:

for (let x = 0; x < patternSize; x += cellSize) {
  for (let y = 0; y < patternSize; y += cellSize) {
  }
}

Within the second nested loop, we can begin to render stuff!

First off, let’s choose a random color for the current shape:

const color = colors[random.int(0, colors.length - 1)];

ctx.fillStyle = color;

Next, let’s store a reference to the current cell’s center x and y position:

const cx = x + cellSize / 2;
const cy = y + cellSize / 2;

In this worklet, we are positioning all of our shapes relative to their center point. While we are here, let’s add some utility functions to our worklet file to help us quickly render center-aligned shape objects. These can live outside of the Worklet class:

function circle(ctx, cx, cy, radius) {
  ctx.beginPath();
  ctx.arc(cx, cy, radius, 0, Math.PI * 2);
  ctx.closePath();
}

function arc(ctx, cx, cy, radius) {
  ctx.beginPath();
  ctx.arc(cx, cy, radius, 0, Math.PI * 1);
  ctx.closePath();
}

function rectangle(ctx, cx, cy, size) {
  ctx.beginPath();
  ctx.rect(cx - size / 2, cy - size / 2, size, size);
  ctx.closePath();
}

function triangle(ctx, cx, cy, size) {
  const originX = cx - size / 2;
  const originY = cy - size / 2;
  ctx.beginPath();
  ctx.moveTo(originX, originY);
  ctx.lineTo(originX + size, originY + size);
  ctx.lineTo(originX, originY + size);
  ctx.closePath();
}

I won’t go into too much detail here, but here’s a diagram visualizing how each of these functions work:

If you get stuck on the graphics rendering part of any of the worklets in this tutorial, look at the MDN docs on HTML canvas. The syntax/usage is almost identical to the 2D graphics context available in Paint API worklets.

Cool! Let’s head back over to our paint() function’s nested loop. The next thing we need to do is choose what shape to render. To do so, we can pick a random string from an array of possibilities:

const shapeChoice = ["circle", "arc", "rectangle", "triangle"][
  random.int(0, 3)
];

We can also pick a random rotation amount in a very similar way:

const rotationDegrees = [0, 90, 180][random.int(0, 2)];

Perfect. We are ready to render!

To start, let’s save our drawing context’s state, just like in the previous worklet:

ctx.save();

Next, we can translate to the center point of the current cell and rotate the canvas using the random value we just chose:

ctx.translate(cx, cy);
ctx.rotate((rotationDegrees * Math.PI) / 180);
ctx.translate(-cx, -cy);

Now we can render the shape itself! Let’s pass our shapeChoice variable to a switch statement and use it to decide which shape rendering function to run:

switch (shapeChoice) {
  case "circle":
    circle(ctx, cx, cy, cellSize / 2);
    break;
  case "arc":
    arc(ctx, cx, cy, cellSize / 2);
    break;
  case "rectangle":
    rectangle(ctx, cx, cy, cellSize);
    break;
  case "triangle":
    triangle(ctx, cx, cy, cellSize);
    break;
}

ctx.fill();

Finally, all we need to do is restore() our drawing context ready for the next shape:

ctx.restore();

With that, our Bauhaus Grids worklet is complete!

Next steps

There are so many directions you could take this worklet. How could you parameterize it further? Could you add a “bias” for specific shapes/colors? Could you add more shape types?

Always experiment — following along with the examples we are creating together is an excellent start, but the best way to learn is to make your own stuff! If you are stuck for inspiration, take a peek at some patterns on Dribbble, look to your favorite artists, the architecture around you, nature, you name it!

As a simple example, here’s the same worklet, in an entirely different color scheme:

Pattern #3 (Voronoi Arcs)

So far, we have created both a chaotic pattern and one that aligns strictly to a grid. For our last example, let’s build one that sits somewhere between the two.

As one last reminder, here’s the finished pattern:

Before we jump in and write any code, let’s take a look at how this worklet… works.

A brief introduction to Voronoi tessellations

As suggested by the name, this worklet uses something called a Voronoi tessellation to calculate its layout. A Voronoi tessellation (or diagram) is, in short, a way to partition a space into non-overlapping polygons.

We add a collection of points to a 2D space. Then for each point, calculate a polygon that contains only it and no other points. Once calculated, the polygons can be used as a kind of “grid” to position anything.

Here’s an animated example:

The fascinating thing about Voronoi-based layouts is that they are responsive in a rather unusual way. As the points in a Voronoi tessellation move around, the polygons automatically re-arrange themselves to fill the space!

Try resizing the element below and watch what happens!

Cool, right?

If you would like to learn more about all things Voronoi, I have an article that goes in-depth. For now, though, this is all we need.

Updating the worklet’s name

Alright, folks, we know the deal here. Let’s fork the starter Pen, update the JavaScript import, and change the worklet’s name and references:

class VoronoiPattern {
  // ...
}

if (typeof registerPaint !== "undefined") {
  registerPaint("voronoiPattern", VoronoiPattern);
}
.worklet-canvas {
  /* ... */
  background-image: paint(voronoiPattern);
}

Defining the worklet’s input properties

Our VoronoiPattern worklet expects the following input properties:

  • --pattern-seed — a seed value for the pseudo-random number generator
  • --pattern-colors — the available colors for each arc/circle in the pattern
  • --pattern-background — the pattern’s background color

Let’s add these input properties to our worklet:

class VoronoiPattern {
  static get inputProperties() {
    return ["--pattern-seed", "--pattern-colors", "--pattern-background"];
  }
  // ...
}

…and register them in our CSS:

@property --pattern-seed {
  syntax: "<number>";
  initial-value: 123456;
  inherits: true;
}

@property --pattern-background {
  syntax: "<color>";
  inherits: false;
  initial-value: #141b3d;
}

@property --pattern-colors {
  syntax: "<color>#";
  initial-value: #e9edeb, #66aac6, #e63890;
  inherits: true;
}

Nice! We are all set. Overalls on, friends — let us paint.

The paint() function

First, let’s clear out the starter worklet’s paint() function, retaining only the width and height definitions. We can then create some variables using our input properties, and seed our pseudo-random number generator, too. Just like in our previous examples:

paint(ctx, geometry, props) {
  const { width, height } = geometry;

  const seed = props.get("--pattern-seed").value;
  const background = props.get("--pattern-background").toString();
  const colors = props.getAll("--pattern-colors").map((c) => c.toString());

  random.use(seedrandom(seed));
}

Before we do anything else, let’s paint a quick background color:

ctx.fillStyle = background;
ctx.fillRect(0, 0, width, height);

Next, let’s import a helper function that will allow us to quickly cook up a Voronoi tessellation:

import { createVoronoiTessellation } from "https://cdn.skypack.dev/@georgedoescode/generative-utils";

This function is essentially a wrapper around d3-delaunay and is part of my generative-utils repository. You can view the source code on GitHub. With “classic” data structures/algorithms such as Voronoi tessellations, there is no need to reinvent the wheel — unless you want to, of course!

Now that we have our createVoronoiTessellation function available, let’s add it to paint():

const { cells } = createVoronoiTessellation({
  width,
  height,
  points: [...Array(24)].map(() => ({
    x: random.float(0, width),
    y: random.float(0, height)
  }))
});

Here, we create a Voronoi Tessellation at the width and height of the worklet’s target element, with 24 controlling points.

Awesome. Time to render our shapes! Lots of this code should be familiar to us, thanks to the previous two examples.

First, we loop through each cell in the tessellation:

cells.forEach((cell) => {
});

For each cell, the first thing we do is choose a color:

ctx.fillStyle = colors[random.int(0, colors.length - 1)];

Next, we store a reference to the center x and y values of the cell:

const cx = cell.centroid.x;
const cy = cell.centroid.y;

Next, we save the context’s current state and rotate the canvas around the cell’s center point:

ctx.save();

ctx.translate(cx, cy);
ctx.rotate((random.float(0, 360) / 180) * Math.PI);
ctx.translate(-cx, -cy);

Cool! Now, we can render something. Let’s draw an arc with an end angle of either PI or PI * 2. To me and you, a semi-circle or a circle:

ctx.beginPath();
ctx.arc(
  cell.centroid.x,
  cell.centroid.y,
  cell.innerCircleRadius * 0.75,
  0,
  Math.PI * random.int(1, 2)
);
ctx.fill();

Our createVoronoiTessellation function attaches a special innerCircleRadius to each cell — this is the largest possible circle that can fit at its center without touching any edges. Think of it as a handy guide for scaling objects to the bounds of a cell. In the snippet above, we are using innerCircleRadius to determine the size of our arcs.

Here’s a simple pen highlighting what’s happening here:

Now that we have added a “primary” arc to each cell, let’s add another one, 25% of the time. This time, however, we can set the arc’s fill color to our worklets background color. Doing so gives us the effect of a little hole in the middle of some of the shapes!

if (random.float(0, 1) > 0.25) {
  ctx.fillStyle = background;
  ctx.beginPath();
  ctx.arc(
    cell.centroid.x,
    cell.centroid.y,
    (cell.innerCircleRadius * 0.75) / 2,
    0,
    Math.PI * 2
  );
  ctx.fill();
}

Great! All we need to do now is restore the drawing context:

ctx.restore();

And, that’s it!

Next steps

The beautiful thing about Voronoi tessellations is that you can use them to position anything at all. In our example, we used arcs, but you could render rectangles, lines, triangles, whatever! Perhaps you could even render the outlines of the cells themselves?

Here’s a version of our VoronoiPattern worklet that renders lots of small lines, rather than circles and semicircles:

Randomizing patterns

You may have noticed that up until this point, all of our patterns have received a static --pattern-seed value. This is fine, but what if we would like our patterns to be random each time they display? Well, lucky for us, all we need to do is set the --pattern-seed variable when the page loads to be a random number. Something like this:

document.documentElement.style.setProperty('--pattern-seed', Math.random() * 10000);

We touched on this briefly earlier, but this is a lovely way to make sure a webpage is a tiny bit different for everyone that sees it.

Until next time

Well, friends, what a trip!

We have created three beautiful patterns together, learned lots of handy Paint API tricks, and (hopefully!) had some fun, too. From here, I hope you feel inspired to make some more generative art/design with CSS Houdini! I’m not sure about you, but I feel like my portfolio site needs a new coat of paint…

Until next time, fellow CSS magicians!

Oh! Before you go, I have a challenge for you. There is a generative Paint API worklet running on this very page! Can you spot it?

  1. There are certainly ways around this rule, but they can be complex and not entirely suitable for this tutorial.

The post Creating Generative Patterns with The CSS Paint API appeared first on CSS-Tricks. You can support CSS-Tricks by being an MVP Supporter.



from CSS-Tricks https://ift.tt/3r7xXTI
via IFTTT

Passkeys: What the Heck and Why?

These things called  passkeys  sure are making the rounds these days. They were a main attraction at  W3C TPAC 2022 , gained support in  Saf...