Archiwum kategorii: CSS

Weekly Platform News: Emoji String Length, Issues with Rounded Buttons, Bundled Exchanges

Post pobrano z: Weekly Platform News: Emoji String Length, Issues with Rounded Buttons, Bundled Exchanges

In this week’s roundup, the string length of two emojis is not always equal, something to consider before making that rounded button, and we may have a new way to share web apps between devices, even when they are offline.

The JavaScript string length of emoji characters

A single rendered emoji can have a JavaScript string length of up to 7 if it contains additional Unicode scalar values that represent a skin tone modifier, gender specification, and multicolor rendering.

(via Henri Sivonen)

An accessibility issue with rounded buttons

Be aware that applying CSS border-radius to a <button> element reduces the button’s interactive area (“those lost corner pixels are no longer clickable”).

You can avoid this accessibility issue in CSS, e.g., by emulating rounded corners via border-image instead, or by overlaying the button with an absolutely positioned, transparent ::before pseudo-element.

(via Tyler Sticka)

Sharing web pages while offline with Bundled Exchanges

Chrome plans to add support for navigation to Bundled Exchanges (part of Web Packaging). A bundled exchangeis a collection of HTTP request/response pairs, and it can be used to bundle a web page and all of its resources.

The browser should be able to parse and verify the bundle’s signature and then navigate to the website represented by the bundle without actually connecting to the site as all the necessary subresources could be served by the bundle.

Kinuko Yasuda from Google has posted a video that demonstrates how Bundled Exchanges enable sharing web pages (e.g., a web game) with other devices while offline.

(via Kinuko Yasuda)


Read even more news in my weekly Sunday issue, which can be delivered to you via email every Monday morning. Visit webplatform.news for more information.

The post Weekly Platform News: Emoji String Length, Issues with Rounded Buttons, Bundled Exchanges appeared first on CSS-Tricks.

Buddy on CSS-Tricks

Post pobrano z: Buddy on CSS-Tricks

Here’s a little direct product endorsement for ya: I literally use Buddy for deployment on all my projects.

Buddy isn’t just a deployment tool, we’ll get to that, but it’s something that Buddy does very well and definitely a reason you might look at picking it up yourself if you’re looking around for a reliable, high-quality deployment service.

Here’s my current setup:

  • CSS-Tricks is WordPress site.
  • The whole wp-content folder is a private repository on GitHub.
  • The hosting is on Flywheel, which gives me SFTP access to the server.
  • When I push to the Master branch, Buddy automatically deploys the changed files over SFTP. This is fast because the fact it’s only dealing with changed files.

The setup on Buddy for this is incredibly nice and simple and I’ve never once had any problems with it. You may want to look at zero-downtime deployments as well, where files are uploaded to a separate directory first and swapped out with the destination directories if the entire upload is successful.

And I don’t just use this setup for CSS-Tricks but all my sites that need this kind of deployment.

But like I said, Buddy isn’t just deployment. Buddy is all about pipelines. You (visually) configure a bunch of tasks that you want Buddy to do for you and the trigger that kicks it off. Pushing to Master is just one possible trigger, you can also kick them off manually or on a timer.

What tasks? Well, a common one would be running your tests. You know: Continuous Integration (CI) and Continuous Development (CD). You can tell Buddy to run whatever terminal commands you want (they’ll spin up a Docker container for you), so however you run tests and get output will work just fine.

You could have it shoot you an email, hit some other web service, or run a build process.

Here’s the actual tasks I run in my pipeline right now:

  1. Upload the files over SFTP
  2. Tell Cloudflare to purge all the cache on the site
  3. Send a message to a particular channel on Slack (also do that on failure)

So useful.

It’s so easy to set up it almost encourages doing more with your pipelines. I need to get some Cypress tests in there and I’d love to integrate an action to automatically optimize all images in the commits.

The post Buddy on CSS-Tricks appeared first on CSS-Tricks.

Where should “Subscribe to Podcast” link to?

Post pobrano z: Where should “Subscribe to Podcast” link to?

For a while, iTunes was the big dog in podcasting, so if you linked „Subscribe to Podcast” to like:

https://podcasts.apple.com/podcast/id493890455

…that would make sense. It’s a web URL anyway, so it will work for anyone and has information about the podcast, as well as a list of recent shows you can even listen to right there. For Apple folks, you might be redirected in-app (mobile) or it becomes one click away (desktop). But for folks on Android or Linux or Windows or something, that’s not particularly useful.

What are the other possibilities?

Podcasts are essentially dressed up RSS, so giving people a link to the feed isn’t out of the question. We do that on both ShopTalk and CodePen Radio:

I like PocketCasts for my podcasts. I feel like this used to be more obvious, but pasting in an RSS link to search does seem to find the feeds.

I would think (and hope!) that most podcast apps have some way to subscribe manually via feed. But… pretty nerdy and probably a little too dangerous for just a „Subscribe to Podcast” link.

For Android specifically, there is a site where you can put your feed URL after „subscribeonandroid.com” and get a special page just for that:

https://subscribeonandroid.com/blog.codepen.io/feed/podcast/

They say:

If the listener has a one click supported app on their android device, the App will load automatically.

And clearly there are some options:

I find the most common option on podcasts is to link to a soup of popular options:

I think that’s probably a safe thing to do. For one, it signals that you’re on top of your game a bit and that your show is working on major platforms. But more importantly, podcast listeners probably know what platform they mainly use and clicking on a link specifically for that platform is probably quite natural.

Speaking of major platforms, Spotify is going big on podcasts, so linking directly to Spotify probably isn’t the worst choice you could make.

https://open.spotify.com/show/2PUoQB330ft0sTzSNoCPrH?si=ZUYOtZSZQZyrDdo81l7TcA

But there are situations where you only get one link. Instagram is notable for this. No links on posts — only the one link on your profile. You could send them to your website, but of course, with podcasts, the name of the game is making it easy to subscribe. That means getting people right there is best. But also with stuff like tweets, you can’t always deliver a smorgasbord of links. Hence the title of this blog post. If you gotta link to just one place to subscribe, where should it be?

Looks like that’s what Plink does.

Here’s ShopTalk: https://plnk.to/shoptalk

Visiting on desktop gets you the smorgasbord of links. Visiting on my iPhone, I get a direct link to Apple Podcasts.

That’s what they do:

Auto-open installed Podcast Apps native to listener’s iOS, Android, and other mobile and smart watch devices. Each smart link also has a Show Page that desktop users will see with links to that show in Apps like Apple & Google Podcasts, Spotify, Stitcher, Overcast, and other podcatchers.

They apparently use all kinds of data to figure it out.

… will detect the listener’s device, geo, and other factors and send them to your show in pre-installed podcast apps.

Anybody can make a redirect link to particular platforms. Like, we could have built shoptalkshow.com/spotify and shoptalkshow.com/itunes and redirected to those places, but what you get here is fancy auto-detection in a single link.

I signed up for it for ShopTalk, so we’ll see if we end up using it much or not.

The post Where should “Subscribe to Podcast” link to? appeared first on CSS-Tricks.

Where should “Subscribe to Podcast” link to?

Post pobrano z: Where should “Subscribe to Podcast” link to?

For a while, iTunes was the big dog in podcasting, so if you linked „Subscribe to Podcast” to like:

https://podcasts.apple.com/podcast/id493890455

…that would make sense. It’s a web URL anyway, so it will work for anyone and has information about the podcast, as well as a list of recent shows you can even listen to right there. For Apple folks, you might be redirected in-app (mobile) or it becomes one click away (desktop). But for folks on Android or Linux or Windows or something, that’s not particularly useful.

What are the other possibilities?

Podcasts are essentially dressed up RSS, so giving people a link to the feed isn’t out of the question. We do that on both ShopTalk and CodePen Radio:

I like PocketCasts for my podcasts. I feel like this used to be more obvious, but pasting in an RSS link to search does seem to find the feeds.

I would think (and hope!) that most podcast apps have some way to subscribe manually via feed. But… pretty nerdy and probably a little too dangerous for just a „Subscribe to Podcast” link.

For Android specifically, there is a site where you can put your feed URL after „subscribeonandroid.com” and get a special page just for that:

https://subscribeonandroid.com/blog.codepen.io/feed/podcast/

They say:

If the listener has a one click supported app on their android device, the App will load automatically.

And clearly there are some options:

I find the most common option on podcasts is to link to a soup of popular options:

I think that’s probably a safe thing to do. For one, it signals that you’re on top of your game a bit and that your show is working on major platforms. But more importantly, podcast listeners probably know what platform they mainly use and clicking on a link specifically for that platform is probably quite natural.

Speaking of major platforms, Spotify is going big on podcasts, so linking directly to Spotify probably isn’t the worst choice you could make.

https://open.spotify.com/show/2PUoQB330ft0sTzSNoCPrH?si=ZUYOtZSZQZyrDdo81l7TcA

But there are situations where you only get one link. Instagram is notable for this. No links on posts — only the one link on your profile. You could send them to your website, but of course, with podcasts, the name of the game is making it easy to subscribe. That means getting people right there is best. But also with stuff like tweets, you can’t always deliver a smorgasbord of links. Hence the title of this blog post. If you gotta link to just one place to subscribe, where should it be?

Looks like that’s what Plink does.

Here’s ShopTalk: https://plnk.to/shoptalk

Visiting on desktop gets you the smorgasbord of links. Visiting on my iPhone, I get a direct link to Apple Podcasts.

That’s what they do:

Auto-open installed Podcast Apps native to listener’s iOS, Android, and other mobile and smart watch devices. Each smart link also has a Show Page that desktop users will see with links to that show in Apps like Apple & Google Podcasts, Spotify, Stitcher, Overcast, and other podcatchers.

They apparently use all kinds of data to figure it out.

… will detect the listener’s device, geo, and other factors and send them to your show in pre-installed podcast apps.

Anybody can make a redirect link to particular platforms. Like, we could have built shoptalkshow.com/spotify and shoptalkshow.com/itunes and redirected to those places, but what you get here is fancy auto-detection in a single link.

I signed up for it for ShopTalk, so we’ll see if we end up using it much or not.

The post Where should “Subscribe to Podcast” link to? appeared first on CSS-Tricks.

Ghost Buttons with Directional Awareness in CSS

Post pobrano z: Ghost Buttons with Directional Awareness in CSS

It would surprise me if you’d never come across a ghost button 👻. You know the ones: they have a transparent background that fills with a solid color on hover. Smashing Magazine has a whole article going into the idea. In this article, we’re going to build a ghost button, but that will be the easy part. The fun and tricky part will be animating the fill of that ghost button such that the background fills up in the direction from which a cursor hovers over it.

Here’s a basic starter for a ghost button:

See the Pen
Basic Ghost Button 👻
by Jhey (@jh3y)
on CodePen.

In most cases, the background-color has a transition to a solid color. There are designs out there where the button might fill from left to right, top to bottom, etc., for some visual flair. For example, here’s left-to-right:

See the Pen
Directional filling Ghost Button 👻
by Jhey (@jh3y)
on CodePen.

There’s a UX nitpick here. It feels off if you hover against the fill. Consider this example. The button fills from the left while you hover from the right.

Hover feels off 👎

It is better if the button fills from our initial hover point.

Hover feels good 👍

So, how can we give the button directional awareness? Your initial instinct might be to reach for a JavaScript solution, but we can create something with CSS and a little extra markup instead.

For those in camp TL;DR, here are some pure CSS ghost buttons with directional awareness!

See the Pen
Pure CSS Ghost Buttons w/ Directional Awareness ✨👻😎
by Jhey (@jh3y)
on CodePen.

Let’s build this thing step by step. All the code is available in this CodePen collection.

Creating a foundation

Let’s start by creating the foundations of our ghost button. The markup is straightforward.

<button>Boo!</button>

Our CSS implementation will leverage CSS custom properties. These make maintenance easier. They also make for simple customization via inline properties.

button {
  --borderWidth: 5;
  --boxShadowDepth: 8;
  --buttonColor: #f00;
  --fontSize: 3;
  --horizontalPadding: 16;
  --verticalPadding: 8;

  background: transparent;
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  box-shadow: calc(var(--boxShadowDepth) * 1px) calc(var(--boxShadowDepth) * 1px) 0 #888;
  color: var(--buttonColor);
  cursor: pointer;
  font-size: calc(var(--fontSize) * 1rem);
  font-weight: bold;
  outline: transparent;
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  transition: box-shadow 0.15s ease;
}

button:hover {
  box-shadow: calc(var(--boxShadowDepth) / 2 * 1px) calc(var(--boxShadowDepth) / 2 * 1px) 0 #888;
}

button:active {
  box-shadow: 0 0 0 #888;
}

Putting it all together gives us this:

See the Pen
Ghost Button Foundation 👻
by Jhey (@jh3y)
on CodePen.

Great! We have a button and a hover effect, but no fill to go with it. Let’s do that next.

Adding a fill

To do this, we create elements that show the filled state of our ghost button. The trick is to clip those elements with clip-path and hide them. We can reveal them when we hover over the button by transitioning the clip-path.

Child element with a 50% clip

They must line up with the parent button. Our CSS variables will help a lot here.

At first thought, we could have reached for pseudo-elements. There won’t be enough pseudo-elements for every direction though. They will also interfere with accessibility… but more on this later.

Let’s start by adding a basic fill from left to right on hover. First, let’s add a span. That span will need the same text content as the button.

<button>Boo!
  <span>Boo!</span>
</button>

Now we need to line our span up with the button. Our CSS variables will do the heavy lifting here.

button span {
  background: var(--buttonColor);
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  bottom: calc(var(--borderWidth) * -1px);
  color: var(--bg, #fafafa);
  left: calc(var(--borderWidth) * -1px);
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
}

Finally, we clip the span out of view and add a rule that will reveal it on hover by updating the clip. Defining a transition will give it that cherry on top.

button span {
  --clip: inset(0 100% 0 0);
  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
  transition: clip-path 0.25s ease, -webkit-clip-path 0.25s ease;
  // ...Remaining div styles
}

button:hover span {
  --clip: inset(0 0 0 0);
}

See the Pen
Ghost Button w/ LTR fill 👻
by Jhey (@jh3y)
on CodePen.

Adding directional awareness

So, how might we add directional awareness? We need four elements. Each element will be responsible for detecting a hover entry point. With clip-path, we can split the button area into four segments.

Four :hover segments

Let’s add four spans to a button and position them to fill the button.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</button>
button span {
  background: var(--bg);
  bottom: calc(var(--borderWidth) * -1px);
  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
  left: calc(var(--borderWidth) * -1px);
  opacity: 0.5;
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
  z-index: 1;
}

We can target each element and assign a clip and color with CSS variables.

button span:nth-of-type(1) {
  --bg: #00f;
  --clip: polygon(0 0, 100% 0, 50% 50%, 50% 50%);
}
button span:nth-of-type(2) {
  --bg: #f00;
  --clip: polygon(100% 0, 100% 100%, 50% 50%);
}
button span:nth-of-type(3) {
  --bg: #008000;
  --clip: polygon(0 100%, 100% 100%, 50% 50%);
}
button span:nth-of-type(4) {
  --bg: #800080;
  --clip: polygon(0 0, 0 100%, 50% 50%);
}

Cool. To test this, let’s change the opacity on hover.

button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
  opacity: 1;
}
So close

Uh-oh. There’s an issue here. If we enter and hover one segment but then hover over another, the fill direction would change. That’s going to look off. To fix this, we can set a z-index and clip-path on hover so that a segment fills the space.

button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
  --clip: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  opacity: 1;
  z-index: 2;
}

See the Pen
Pure CSS Directional Awareness w/ clip-path 👻
by Jhey (@jh3y)
on CodePen.

Putting it all together

We know how to create the fill animation, and we know how to detect direction. How can we put the two together? Use the sibling combinator!

Doing so means when we hover a directional segment, we can reveal a particular fill element.

First, let’s update the markup.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <b>Boo!</b>
  <b>Boo!</b>
  <b>Boo!</b>
  <b>Boo!</b>
</button>

Now, we can update the CSS. Referring to our left-to-right fill, we can reuse the styling. We only need to set a specific clip-path for each element. I’ve approached the ordering the same as some property values. The first child is top, the second is right, and so on.

button b:nth-of-type(1) {
  --clip: inset(0 0 100% 0);
}
button b:nth-of-type(2) {
  --clip: inset(0 0 0 100%);
}
button b:nth-of-type(3) {
  --clip: inset(100% 0 0 0);
}
button b:nth-of-type(4) {
  --clip: inset(0 100% 0 0);
}

The last piece is to update the clip-path for the relevant element when hovering the paired segment.

button span:nth-of-type(1):hover ~ b:nth-of-type(1),
button span:nth-of-type(2):hover ~ b:nth-of-type(2),
button span:nth-of-type(3):hover ~ b:nth-of-type(3),
button span:nth-of-type(4):hover ~ b:nth-of-type(4) {
  --clip: inset(0 0 0 0);
}

Tada! We have a pure CSS ghost button with directional awareness.

See the Pen
Pure CSS Ghost Button w/ Directional Awareness 👻
by Jhey (@jh3y)
on CodePen.

Accessibility

In its current state, the button isn’t accessible.

The extra markup is read by VoiceOver.

Those extra elements aren’t helping much as a screen reader will repeat the content four times. We need to hide those elements from a screen reader.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
</button>

No more repeated content.

See the Pen
Accessible Pure CSS Ghost Button w/ Directional Awareness 👻
by Jhey (@jh3y)
on CodePen.

That’s it!

With a little extra markup and some CSS trickery, we can create ghost buttons with directional awareness. Use a preprocessor or put together a component in your app and you won’t need to write out all the HTML, too.

Here’s a demo making use of inline CSS variables to control the button color.

See the Pen
Pure CSS Ghost Buttons w/ Directional Awareness ✨👻😎
by Jhey (@jh3y)
on CodePen.

The post Ghost Buttons with Directional Awareness in CSS appeared first on CSS-Tricks.

Weekly Platform News: Apple Deploys Web Components, Progressive HTML Rendering, Self-Hosting Critical Resources

Post pobrano z: Weekly Platform News: Apple Deploys Web Components, Progressive HTML Rendering, Self-Hosting Critical Resources

In this week’s roundup, Apple gets into web components, how Instagram is insta-loading scripts, and some food for thought for self-hosting critical resources.

Apple deploys web components built using Stencil

The new Apple Music web app (beta) uses a JavaScript framework (Ember.js) but also standard web components such as <apple-music-video-player> that are built using Stencil, a web component compiler.

Stencil is a build-time tool that generates standard web components with minimal overhead, while providing core features such as templating, state management, and routing, as well as performance features such as code-splitting and lazy-loading.

Apple just deployed into production nearly 50 web components powering a major app they have a significant amount of revenue and strategic value riding on. You can’t say that “no one uses web components” or they are “solving problems that don‘t exist or have been solved better in user land” with a straight face anymore.

(via Max Lynch)

Instagram makes use of chunked transfer encoding and progressive HTML rendering

Instagram’s website uses HTTP chunked transfer encoding to stream the contents of the HTML document to the browser as each part of the page is generated on the server.

We can flush the HTML <head> to the browser almost immediately … This allows the browser to start downloading scripts and stylesheets while the server is busy generating the dynamic data in the rest of the page.

They also use this technique to flush JSON data to the page in <script> elements. The client script waits for this data (using Promise) instead of requesting it via XHR.

(via Glenn Conner)

Consider self-hosting your critical resources

One section of University of Notre Dame’s website used to load jQuery from Google’s CDN, which could result in very long loading times (100+ seconds) when visiting the site from China. They’ve resolved the issue by self-hosting jQuery instead.

(via Erik Runyon)


Read even more news in my weekly Sunday issue. Visit webplatform.news for more information.

The post Weekly Platform News: Apple Deploys Web Components, Progressive HTML Rendering, Self-Hosting Critical Resources appeared first on CSS-Tricks.

Simplicity

Post pobrano z: Simplicity

Earlier this week, Bastian Allgeier published some interesting thoughts about complexity in web development and how changing simple things can often feel far more difficult than they need to be:

You want to build a JS file? Please update Webpack first. Oh, that new version of Webpack is no longer compatible with your Node version. Oh, your new Node version is no longer compatible with that other dependency. Oh, now you have 233 detected security issues in all your node_modules but you can’t fix them because that would break something completely unrelated.

It’s a UX nightmare and I haven’t found a single exception yet. Vue Cli or Parcel are the most positive examples, where positive means: not as horrible as the rest.

This dependency hell is also the reason why old projects are almost like sealed capsules. You can hardly let a project lie around for more than a year, because afterwards it’s probably broken.

A couple of weeks ago, I returned to a web app that was built with a variety of tools I hadn’t updated in quite some time and realized that it would be an enormous effort to fix all the packages and dependencies; instead I should just start over again. I can certainly empathise with Bastian on this stuff.

This reminds me that Chris wrote a great essay not so long about simple web development and collected a ton of thoughts from other developers out there.

Direct Link to ArticlePermalink

The post Simplicity appeared first on CSS-Tricks.

Using a PostCSS function to automate your responsive workflow

Post pobrano z: Using a PostCSS function to automate your responsive workflow

A little while back, you might have bumped into this CSS-Tricks article where I described how a mixin can be used to automate responsive font sizes using RFS. In its latest version, v9, RFS is capable of rescaling any value for value for any CSS property with px or rem units, like margin, padding, border-radius or even box-shadow.

Today, we’ll focus on its PostCSS implementation. Czytaj dalej Using a PostCSS function to automate your responsive workflow

Learn Design for Developers and SVG Animation with Sarah Drasner ✨💖

Post pobrano z: Learn Design for Developers and SVG Animation with Sarah Drasner ✨💖

(This is a sponsored post.)

Hey, Marc here from Frontend Masters — excited to support CSS-Tricks ❤️!

Have you checked out Sarah Drasner’s courses yet? She has two awesome courses on Design for Developers and SVG! Plus another introducing Vue.js!

Design for Developers

In the Design for Developers course, you’ll learn to become self-sufficient throughout the entire lifecycle of the project — from concept to design to implementation!

You’ll learn to…

  • Code dynamic layouts and understand the principles of composition.
  • Select the typography that compliments your design by following simple rules.
  • Choose colors palettes, and understand the theories to understand why they work together.
  • Know how and when to reach for design tools such as Photoshop or Sketch.
  • Use the correct image formats for performance.
  • Prototype to quickly communicate your ideas and get your layout up-and-running.

SVG Essentials and Animation

In the SVG Essentials and Animation course, you’ll learn to build and optimize SVG – the scalable graphics format for the web that can achieve impressively small file sizes for fast-loading websites.

You’ll learn to…

  • 〰 Create platonic and custom shapes with path commands.
  • ⚡️ Optimize SVG to achieve smaller file sizes for performance.
  • 💻 Assemble new SVGs with code and graphics programs.
  • 🖱 Control complex animations and timelines with user input.
  • 💥 Leverage GreenSock’s JavaScript libraries for immersive animation effects.

Introduction to Vue.js

In the Introduction to Vue.js course, you’ll get started quickly with the Vue.js JavaScript framework!

You’ll learn to…

  • Build custom, reusable components and animate them.
  • Use props, slots, and scoped styles to create flexible components.
  • Grok advanced features like filters and mixins for transforming data.
  • Get a single page application up and running fast with the Vue-CLI.
  • Work with Vuex to manage the state of larger-scale applications.

This course is for developers with an intermediate knowledge of JavaScript who want to learn how to build and maintain complex applications quickly and efficiently.

You’ll love Sarah’s awesome courses!

Direct Link to ArticlePermalink

The post Learn Design for Developers and SVG Animation with Sarah Drasner ✨💖 appeared first on CSS-Tricks.

Various Methods for Expanding a Box While Preserving the Border Radius

Post pobrano z: Various Methods for Expanding a Box While Preserving the Border Radius

I’ve recently noticed an interesting change on CodePen: on hovering the pens on the homepage, there’s a rectangle with rounded corners expanding in the back.

Animated gif recording the CodePen expanding box effect on hover.
Expanding box effect on the CodePen homepage.

Being the curious creature that I am, I had to check how this works! Turns out, the rectangle in the back is an absolutely positioned ::after pseudo-element.

Collage. On the left side, there is a DevTools screenshot showing the initial styles applied on the ::after pseudo-element. The relevant ones are those making it absolutely positioned with an offset of 1rem from the top and left and with an offset of -1rem from the right and bottom. On the right side, we have an illustration of these styles, showing the parent element box, the ::after box and the offsets between their edges.
Initial ::after styles. A positive offset goes inwards from the parent’s padding limit, while a negative one goes outwards.

On :hover, its offsets are overridden and, combined with the transition, we get the expanding box effect.

Collage. On the left side, there is a DevTools screenshot showing the :hover styles applied on the ::after pseudo-element. These are all offsets overriding the initial ones and making the boundary of the ::after shift outwards by 2rem in all directions except the right. On the right side, we have an illustration of these styles, showing the parent element box, the ::after box and the offsets between their edges.
The ::after styles on :hover.

The right property has the same value (-1rem) in both the initial and the :hover rule sets, so it’s unnecessary to override it, but all the other offsets move by 2rem outwards (from 1rem to -1rem for the top and left offsets and from -1rem to -3rem for the bottom offset)

One thing to notice here is that the ::after pseudo-element has a border-radius of 10px which gets preserved as it expands. Which got me to think about what methods we have for expanding/shrinking (pseudo-) elements while preserving their border-radius. How many can you think of? Let me know if you have ideas that haven’t been included below, where we take a look at a bunch of options and see which is best suited for what situation.

Changing offsets

This is the method used on CodePen and it works really well in this particular situation for a bunch of reasons. First off, it has great support. It also works when the expanding (pseudo-) element is responsive, with no fixed dimensions and, at the same time, the amount by which it expands is fixed (a rem value). It also works for expanding in more than two directions (top, bottom and left in this particular case).

There are however a couple of caveats we need to be aware of.

First, our expanding element cannot have position: static. This is not a problem in the context of the CodePen use case since the ::after pseudo-element needs to be absolutely positioned anyway in order to be placed underneath the rest of this parent’s content.

Second, going overboard with offset animations (as well as, in general, animating any property that affects layout with box properties the way offsets, margins, border widths, paddings or dimensions do) can negatively impact performance. Again, this is not something of concern here, we only have a little transition on :hover, no big deal.

Changing dimensions

Instead of changing offsets, we could change dimensions instead. However, this is a method that works if we want our (pseudo-) element to expand in, at most, two directions. Otherwise, we need to change offsets as well. In order to better understand this, let’s consider the CodePen situation where we want our ::after pseudo-elements to expand in three directions (top, bottom and left).

The relevant initial sizing info is the following:

.single-item::after {
  top: 1rem;
  right: -1rem;
  bottom: -1rem;
  left: 1rem;
}

Since opposing offsets (the topbottom and leftright pairs) cancel each other (1rem - 1rem = 0), it results that the pseudo-element’s dimensions are equal to those of its parent (or 100% of the parent’s dimensions).

So we can re-write the above as:

.single-item::after {
  top: 1rem;
  right: -1rem;
  width: 100%;
  height: 100%;
}

On :hover, we increase the width by 2rem to the left and the height by 4rem, 2rem to the top and 2rem to the bottom. However, just writing:

.single-item::after {
  width: calc(100% + 2rem);
  height: calc(100% + 4rem);
}

…is not enough, as this makes the height increase the downward direction by 4rem instead of increasing it by 2rem up and 2rem down. The following demo illustrates this (put :focus on or hover over the items to see how the ::after pseudo-element expands):

See the Pen by thebabydino (@thebabydino) on CodePen.

We’d need to update the top property as well in order to get the desired effect:

.single-item::after {
  top: -1rem;
  width: calc(100% + 2rem);
  height: calc(100% + 4rem);
}

Which works, as it can be seen below:

See the Pen by thebabydino (@thebabydino) on CodePen.

But, to be honest, this feels less desirable than changing offsets alone.

However, changing dimensions is a good solution in a different kind of situation, like when we want to have some bars with rounded corners that expand/shrink in a single direction.

See the Pen by thebabydino (@thebabydino) on CodePen.

Note that, if we didn’t have rounded corners to preserve, the better solution would be to use directional scaling via the transform property.

Changing padding/border-width

Similar to changing the dimensions, we can change the padding or border-width (for a border that’s transparent). Note that, just like with changing the dimensions, we need to also update offsets if expanding the box in more than two dimensions:

See the Pen by thebabydino (@thebabydino) on CodePen.

In the demo above, the pinkish box represents the content-box of the ::after pseudo-element and you can see it stays the same size, which is important for this approach.

In order to understand why it is important, consider this other limitation: we also need to have the box dimensions defined by two offsets plus the width and the height instead of using all four offsets. This is because the padding/ border-width would only grow inwards if we were to use four offsets rather than two plus the width and the height.

See the Pen by thebabydino (@thebabydino) on CodePen.

For the same reason, we cannot have box-sizing: border-box on our ::after pseudo-element.

See the Pen by thebabydino (@thebabydino) on CodePen.

In spite of these limitations, this method can come in handy if our expanding (pseudo-) element has text content we don’t want to see moving around on :hover as illustrated by the Pen below, where the first two examples change offsets/ dimensions, while the last two change paddings/ border widths:

See the Pen by thebabydino (@thebabydino) on CodePen.

Changing margin

Using this method, we first set the offsets to the :hover state values and a margin to compensate and give us the initial state sizing:

.single-item::after {
  top: -1rem;
  right: -1rem;
  bottom: -3rem;
  left: -1rem;
  margin: 2rem 0 2rem 2rem;
}

Then we zero this margin on :hover:

.single-item:hover::after { margin: 0 }

See the Pen by thebabydino (@thebabydino) on CodePen.

This is another approach that works great for the CodePen situation, though I cannot really think of other use cases. Also note that, just like changing offsets or dimensions, this method affects the size of the content-box, so any text content we may have gets moved and rearranged.

Changing font size

This is probably the trickiest one of all and has lots of limitations, the most important of which being we cannot have text content on the actual (pseudo-) element that expands/shrinks — but it’s another method that would work well in the CodePen case.

Also, font-size on its own doesn’t really do anything to make a box expand or shrink. We need to combine it with one of the previously discussed properties.

For example, we can set the font-size on ::after to be equal to 1rem, set the offsets to the expanded case and set em margins that would correspond to the difference between the expanded and the initial state.

.single-item::after {
  top: -1rem;
  right: -1rem;
  bottom: -3rem;
  left: -1rem;
  margin: 2em 0 2em 2em;
  font-size: 1rem;
}

Then, on :hover, we bring the font-size to 0:

.single-item:hover::after { font-size: 0 }

See the Pen by thebabydino (@thebabydino) on CodePen.

We can also use font-size with offsets, though it gets a bit more complicated:

.single-item::after {
  top: calc(2em - 1rem);
  right: -1rem;
  bottom: calc(2em - 3rem);
  left: calc(2em - 1rem);
  font-size: 1rem;
}

.single-item:hover::after { font-size: 0 }

Still, what’s important is that it works, as it can be seen below:

See the Pen by thebabydino (@thebabydino) on CodePen.

Combining font-size with dimensions is even hairier, as we also need to change the vertical offset value on :hover on top of everything:

.single-item::after {
  top: 1rem;
  right: -1rem;
  width: calc(100% + 2em);
  height: calc(100% + 4em);
  font-size: 0;
}

.single-item:hover::after {
  top: -1rem;
  font-size: 1rem
}

Oh, well, at least it works:

See the Pen by thebabydino (@thebabydino) on CodePen.

Same thing goes for using font-size with padding/border-width:

.single-item::after {
  top: 1rem;
  right: -1rem;
  width: 100%;
  height: 100%;
  font-size: 0;
}

.single-item:nth-child(1)::after {
  padding: 2em 0 2em 2em;
}

.single-item:nth-child(2)::after {
  border: solid 0 transparent;
  border-width: 2em 0 2em 2em;
}

.single-item:hover::after {
  top: -1rem;
  font-size: 1rem;
}

See the Pen by thebabydino (@thebabydino) on CodePen.

Changing scale

If you’ve read pieces on animation performance, then you’ve probably read it’s better to animate transforms instead of properties that impact layout, like offsets, margins, borders, paddings, dimensions — pretty much what we’ve used so far!

The first issue that stands out here is that scaling an element also scales its corner rounding, as illustrated below:

See the Pen by thebabydino (@thebabydino) on CodePen.

We can get around this by also scaling the border-radius the other way.

Let’s say we scale an element by a factor $fx along the x axis and by a factor $fy along the y axis and we want to keep its border-radius at a constant value $r.

This means we also need to divide $r by the corresponding scaling factor along each axis.

border-radius: #{$r/$fx}/ #{$r/$fy};
transform: scale($fx, $fy)

See the Pen by thebabydino (@thebabydino) on CodePen.

However, note that with this method, we need to use scaling factors, not amounts by which we expand our (pseudo-) element in this or that direction. Getting the scaling factors from the dimensions and expansion amounts is possible, but only if they’re expressed in units that have a certain fixed relation between them. While preprocessors can mix units like in or px due to the fact that 1in is always 96px, they cannot resolve how much 1em or 1% or 1vmin or 1ch is in px as they lack context. And calc() is not a solution either, as it doesn’t allow us to divide a length value by another length value to get a unitless scale factor.

This is why scaling is not a solution in the CodePen case, where the ::after boxes have dimensions that depend on the viewport and, at the same time, expand by fixed rem amounts.

But if our scale amount is given or we can easily compute it, this is an option to consider, especially since making the scaling factors custom properties we then animate with a bit of Houdini magic can greatly simplify our code.

border-radius: calc(#{$r}/var(--fx))/ calc(#{$r}/var(--fy));
transform: scale(var(--fx), var(--fy))

Note that Houdini only works in Chromium browsers with the Experimental Web Platform features flag enabled.

For example, we can create this tile grid animation:

Looping tile grid animation (Demo, Chrome with flag only)

The square tiles have an edge length $l and with a corner rounding of $k*$l:

.tile {
  width: $l;
  height: $l;
  border-radius: calc(#{$r}/var(--fx))/ calc(#{$r}/var(--fy));
  transform: scale(var(--fx), var(--fy))
}

We register our two custom properties:

CSS.registerProperty({
  name: '--fx', 
  syntax: '<number>', 
  initialValue: 1, 
  inherits: false
});

CSS.registerProperty({
  name: '--fy', 
  syntax: '<number>', 
  initialValue: 1, 
  inherits: false
});

And we can then animate them:

.tile {
  /* same as before */
  animation: a $t infinite ease-in alternate;
  animation-name: fx, fy;
}

@keyframes fx {
  0%, 35% { --fx: 1 }
  50%, 100% { --fx: #{2*$k} }
}

@keyframes fy {
  0%, 35% { --fy: 1 }
  50%, 100% { --fy: #{2*$k} }
}

Finally, we add in a delay depending on the horizontal (--i) and vertical (--j) grid indices in order to create a staggered animation effect:

animation-delay: 
  calc((var(--i) + var(--m) - var(--j))*#{$t}/(2*var(--m)) - #{$t}), 
  calc((var(--i) + var(--m) - var(--j))*#{$t}/(2*var(--m)) - #{1.5*$t})

Another example is the following one, where the dots are created with the help of pseudo-elements:

Looping spikes animation (Demo, Chrome with flag only)

Since pseudo-elements get scaled together with their parents, we need to also reverse the scaling transform on them:

.spike {
  /* other spike styles */
  transform: var(--position) scalex(var(--fx));

  &::before, &::after {
    /* other pseudo styles */
    transform: scalex(calc(1/var(--fx)));
  }
}

Changing… clip-path?!

This is a method I really like, even though it cuts out pre-Chromium Edge and Internet Explorer support.

Pretty much every usage example of clip-path out there has either a polygon() value or an SVG reference value. However, if you’ve seen some of my previous articles, then you probably know there are other basic shapes we can use, like inset(), which works as illustrated below:

Illustration showing what the four values of the inset() function represent. The first one is the offset of the top edge of the clipping rectangle with respect to the top edge of the border-box. The second one is the offset of the right edge of the clipping rectangle with respect to the right edge of the border-box. The third one is the offset of the bottom edge of the clipping rectangle with respect to the bottom edge of the border-box. The fourth one is the offset of the left edge of the clipping rectangle with respect to the left edge of the border-box.
How the inset() function works. (Demo)

So, in order to reproduce the CodePen effect with this method, we set the ::after offsets to the expanded state values and then cut out what we don’t want to see with the help of clip-path:

.single-item::after {
  top: -1rem;
  right: -1rem;
  bottom: -3em;
  left: -1em;
  clip-path: inset(2rem 0 2rem 2rem)
}

And then, in the :hover state, we zero all insets:

.single-item:hover::after {
  clip-path: inset(0)
}

This can be seen in action below:

See the Pen by thebabydino (@thebabydino) on CodePen.

Alright, this works, but we also need a corner rounding. Fortunately, inset() lets us specify that too as whatever border-radius value we may wish.

Here, a 10px one for all corners along both directions does it:

.single-item::after {
  /* same styles as before */
  clip-path: inset(2rem 0 2rem 2rem round 10px)
}

.single-item:hover::after {
  clip-path: inset(0 round 10px)
}

And this gives us exactly what we were going for:

See the Pen by thebabydino (@thebabydino) on CodePen.

Furthermore, it doesn’t really break anything in non-supporting browsers, it just always stays in the expanded state.

However, while this is method that works great for a lot of situations — including the CodePen use case — it doesn’t work when our expanding/shrinking elements have descendants that go outside their clipped parent’s border-box, as it is the case for the last example given with the previously discussed scaling method.

The post Various Methods for Expanding a Box While Preserving the Border Radius appeared first on CSS-Tricks.