Ads
Tuesday, November 30, 2021
Partly Cloudy today!
With a high of F and a low of 30F. Currently, it's 43F and Clear outside.
Current wind speeds: 12 from the Southwest
Pollen: 0
Sunrise: November 30, 2021 at 07:51PM
Sunset: December 1, 2021 at 05:29AM
UV index: 0
Humidity: 41%
via https://ift.tt/2livfew
December 1, 2021 at 10:06AM
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:
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:
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 mask
s 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.
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-image
s in your theme’s stylesheet with Cloudinary-hosted versions. That’s… amazing. There must be some real clever WordPress filter stuff going on.
The Gallery Block is just gravy.
I like seeing this in there:
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.
Advanced Git series:
- Part 1: Creating the Perfect Commit in Git
- Part 2: Branching Strategies in Git
- Part 3: Better Collaboration With Pull Requests
- Part 4: Merge Conflicts
- Part 5: Rebase vs. Merge
- Part 6: Interactive Rebase
- Part 7: Cherry-Picking Commits in Git
- Part 8: Using the Reflog to Restore Lost Commits (You are here!)
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
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:
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
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:
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:
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:
Let’s actually do this and then recover the branch later:
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
:
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:
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!
Advanced Git series:
- Part 1: Creating the Perfect Commit in Git
- Part 2: Branching Strategies in Git
- Part 3: Better Collaboration With Pull Requests
- Part 4: Merge Conflicts
- Part 5: Rebase vs. Merge
- Part 6: Interactive Rebase
- Part 7: Cherry-Picking Commits in Git
- Part 8: Using the Reflog to Restore Lost Commits (You are here!)
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, November 29, 2021
Mostly Clear today!
With a high of F and a low of 35F. Currently, it's 40F and Clear outside.
Current wind speeds: 7 from the South
Pollen: 0
Sunrise: November 29, 2021 at 07:50PM
Sunset: November 30, 2021 at 05:29AM
UV index: 0
Humidity: 40%
via https://ift.tt/2livfew
November 30, 2021 at 10:05AM
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.
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 @keyframe
s? 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
to0
(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 Link — Permalink 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
Sunday, November 28, 2021
Clear today!
With a high of F and a low of 37F. Currently, it's 41F and Clear outside.
Current wind speeds: 9 from the Southwest
Pollen: 0
Sunrise: November 28, 2021 at 07:49PM
Sunset: November 29, 2021 at 05:29AM
UV index: 0
Humidity: 44%
via https://ift.tt/2livfew
November 29, 2021 at 10:04AM
Saturday, November 27, 2021
Mostly Clear today!
With a high of F and a low of 27F. Currently, it's 30F and Clear outside.
Current wind speeds: 4 from the Northwest
Pollen: 0
Sunrise: November 27, 2021 at 07:48PM
Sunset: November 28, 2021 at 05:30AM
UV index: 0
Humidity: 63%
via https://ift.tt/2livfew
November 28, 2021 at 10:04AM
Friday, November 26, 2021
Partly Cloudy today!
With a high of F and a low of 32F. Currently, it's 42F and Clear outside.
Current wind speeds: 6 from the Southwest
Pollen: 0
Sunrise: November 26, 2021 at 07:47PM
Sunset: November 27, 2021 at 05:30AM
UV index: 0
Humidity: 32%
via https://ift.tt/2livfew
November 27, 2021 at 10:05AM
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 theElements
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
Thursday, November 25, 2021
Clear today!
With a high of F and a low of 29F. Currently, it's 34F and Clear outside.
Current wind speeds: 11 from the Southwest
Pollen: 0
Sunrise: November 25, 2021 at 07:46PM
Sunset: November 26, 2021 at 05:31AM
UV index: 0
Humidity: 33%
via https://ift.tt/2livfew
November 26, 2021 at 10:06AM
Wednesday, November 24, 2021
Clouds Early/Clearing Late today!
With a high of F and a low of 16F. Currently, it's 29F and Partly Cloudy outside.
Current wind speeds: 11 from the North
Pollen: 0
Sunrise: November 24, 2021 at 07:45PM
Sunset: November 25, 2021 at 05:31AM
UV index: 0
Humidity: 73%
via https://ift.tt/2livfew
November 25, 2021 at 10:05AM
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 canvasgeometry
— an object containing the width/height dimensions of the worklet’s target elementprops
— 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?
- 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! - 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?
- 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
Mostly Clear today!
With a high of F and a low of 15F. Currently, it's 14F and Clear outside. Current wind speeds: 13 from the Southwest Pollen: 0 S...
-
So you want an auto-playing looping video without sound? In popular vernacular this is the very meaning of the word GIF . The word has stuck...
-
With a high of F and a low of 31F. Currently, it's 37F and Cloudy outside. Current wind speeds: 7 from the Northeast Pollen: 0 S...
-
Last year , we kicked out a roundup of published surveys, research, and other findings from around the web. There were some nice nuggets in ...