Here’s a nifty post by Jonathan Snook where he walks us through how to make a calendar interface with CSS Grid and there’s a lot of tricks in here that are worth digging into a little bit more, particularly where Jonathan uses grid-auto-flow: dense which will let Grid take the wheels of a design and try to fill up as much of the allotted space as possible.
As I was digging around, I found a post on Grid’s auto-placement algorithm by Ian Yates which kinda fleshes things out more succinctly. Might come in handy.
Oh, and we have an example of a Grid-based calendar in our ongoing collection of CSS Grid starter templates.
We only want this div to be visible when it’s hovered, so:
div:hover {
opacity: 1;
}
We need focus styles as well, for accessibility, so:
div:hover,
div:focus {
opacity: 1;
}
But div’s can’t be focused on their own, so we’ll need:
<div tabindex="0">
</div>
There is content in this div. Not just text, but links as well.
<div tabindex="0">
<p>This little piggy went to market.</p>
<a href="#market">Go to market</a>
</div>
This is where it gets tricky.
As soon as focus moves from the div to the anchor link inside it, the div is no longer in focus, which leads to this weird and potentially confusing situation:
In this example, :hover reveals the div, including the link inside. Focusing the div also works, but as soon as you tab to move focus to the link, everything disappears. The link inside can recieve focus, but it’s visually hidden because the div parent is visually hidden.
One solution here is to ensure that the div remains visible when anything inside of it is focused. New CSS has our back here:
But browser support isn’t great for :focus-within. If it was perfect, this is all we would need. In fact we wouldn’t even need :focus because :focus-within handles that also.
But until then, we might need JavaScript to help. How you actually approach this depends, but the idea would be something like…
When a element comes into focus…
If the parent of that element is also focusable, make sure it is visible
When the link leaves focus…
Whatever you did to make sure the parent visible is reversed
There is a lot to consider here, like which elements you actually want to watch, how to make them visible, and how far up the tree you want to go.
Something like this is a very basic approach:
var link = document.querySelector(".deal-with-focus-with-javascript");
link.addEventListener("focus", function() {
link.parentElement.classList.add("focus");
});
link.addEventListener("blur", function() {
link.parentElement.classList.remove("focus");
});
Transitioning border for a hover state. Simple, right? You might be unpleasantly surprised.
The Challenge
The challenge is simple: building a button with an expanding border on hover.
This article will focus on genuine CSS tricks that would be easy to drop into any project without having to touch the DOM or use JavaScript. The methods covered here will follow these rules
Single element (no helper divs, but psuedo-elements are allowed)
CSS only (no JavaScript)
Works for any size (not restricted to a specific width, height, or aspect ratio)
Supports transparent backgrounds
Smooth and performant transition
I proposed this challenge in the Animation at Work Slack and again on Twitter. Though there was no consensus on the best approach, I did receive some really clever ideas by some phenomenal developers.
Method 1: Animating border
The most straightforward way to animate a border is… well, by animating border.
Nice and simple, but there are some big performance issues.
Since border takes up space in the document’s layout, changing the border-width will trigger layout. Nearby elements will shift around because of the new border size, making browser reposition those elements every frame of the animation unless you set an explicit size on the button.
As if triggering layout wasn’t bad enough, the transition itself feels “stepped”. I’ll show why in the next example.
Method 2: Better border with outline
How can we change the border without triggering layout? By using outline instead! You’re probably most familiar with outline from removing it on :focus styles (though you shouldn’t), but outline is an outer line that doesn’t change an element’s size or position in the layout.
.border-button {
outline: solid 5px #FC5185;
transition: outline 0.6s linear;
margin: 0.5em; /* Increased margin since the outline expands outside the element */
}
.border-button:hover { outline-width: 10px; }
A quick check in Dev Tools’ Performance tab shows the outline transition does not trigger layout. Regardless, the movement still seems stepped because browsers are rounding the border-width and outline-width values so you don’t get sub-pixel rendering between 5 and 6 or smooth transitions from 5.4 to 5.5.
Strangely, Safari often doesn’t render the outline transition and occasionally leaves crazy artifacts.
Method 3: Cut it with clip-path
First implemented by Steve Gardner, this method uses clip-path with calc to trim the border down so on hover we can transition to reveal the full border.
.border-button {
/* Full width border and a clip-path visually cutting it down to the starting size */
border: solid 10px #FC5185;
clip-path: polygon(
calc(0% + 5px) calc(0% + 5px), /* top left */
calc(100% - 5px) calc(0% + 5px), /* top right */
calc(100% - 5px) calc(100% - 5px), /* bottom right */
calc(0% + 5px) calc(100% - 5px) /* bottom left */
);
transition: clip-path 0.6s linear;
}
.border-button:hover {
/* Clip-path spanning the entire box so it's no longer hiding the full-width border. */
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
clip-path technique is the smoothest and most performant method so far, but does come with a few caveats. Rounding errors may cause a little unevenness, depending on the exact size. The border also has to be full size from the start, which may make exact positioning tricky.
We can simulate a border using a clever combination of multiple linear-gradient backgrounds properly sized. In total we have four separate gradients, one for each side. The background-position and background-size properties get each gradient in the right spot and the right size, which can then be transitioned to make the border expand.
.border-button {
background-repeat: no-repeat;
/* background-size values will repeat so we only need to declare them once */
background-size:
calc(100% - 10px) 5px, /* top & bottom */
5px calc(100% - 10px); /* right & left */
background-position:
5px 5px, /* top */
calc(100% - 5px) 5px, /* right */
5px calc(100% - 5px), /* bottom */
5px 5px; /* left */
/* Since we're sizing and positioning with the above properties, we only need to set up a simple solid-color gradients for each side */
background-image:
linear-gradient(0deg, #FC5185, #FC5185),
linear-gradient(0deg, #FC5185, #FC5185),
linear-gradient(0deg, #FC5185, #FC5185),
linear-gradient(0deg, #FC5185, #FC5185);
transition: all 0.6s linear;
transition-property: background-size, background-position;
}
.border-button:hover {
background-position: 0 0, 100% 0, 0 100%, 0 0;
background-size: 100% 10px, 10px 100%, 100% 10px, 10px 100%;
}
This method is quite difficult to set up and has quite a few cross-browser differences. Firefox and Safari animate the faux-border smoothly, exactly the effect we’re looking for. Chrome’s animation is jerky and even more stepped than the outline and border transitions. IE and Edge refuse to animate the background at all, but they do give the proper border expansion effect.
Method 5: Fake it with box-shadow
Hidden within box-shadow’s spec is a fourth value for spread-radius. Set all the other length values to 0px and use the spread-radius to build your border alternative that, like outline, won’t affect layout.
.border-button {
box-shadow: 0px 0px 0px 5px #FC5185;
transition: box-shadow 0.6s linear;
margin: 0.5em; /* Increased margin since the box-shado expands outside the element, like outline */
}
.border-button:hover { box-shadow: 0px 0px 0px 10px #FC5185; }
The transition with box-shadow is adequately performant and feels much smoother, except in Safari where it’s snapping to whole-values during the transition like border and outline.
Pseudo-Elements
Several of these techniques can be modified to use a pseudo-element instead, but pseudo-elements ended up causing some additional performance issues in my tests.
For the box-shadow method, the transition occasionally triggered paint in a much larger area than necessary. Reinier Kaper pointed out that a pseudo-element can help isolate the paint to a more specific area. As I ran further tests, box-shadow was no longer causing paint in large areas of the document and the complication of the pseudo-element ended up being less performant. The change in paint and performance may have been due to a Chrome update, so feel free to test for yourself.
I also could not find a way to utilize pseudo-elements in a way that would allow for transform based animation.
Why not transform: scale?
You may be firing up Twitter to helpfully suggest using transform: scale for this. Since transform and opacity are the best style properties to animate for performance, why not use a pseudo-element and have the border scale up & down?
The border will show through a transparent button. I forced a background on the button to show how the border is hiding behind the button. If your design calls for buttons with a full background, then this could work.
You can’t scale the border to specific sizes. Since the button’s dimensions vary with the text, there’s no way to animate the border from exactly 5px to 10px using only CSS. In this example I’ve done some magic-numbers on the scale to get it to appear right, but that won’t be universal.
The border animates unevenly because the button’s aspect ratio isn’t 1:1. This usually means the left/right will appear larger than the top/bottom until the animation completes. This may not be an issue depending on how fast your transition is, the button’s aspect ratio, and how big your border is.
If we loosen our rules a bit, there are many interesting ways you can animate borders. Codrops consistently does outstanding work in this area, usually utilizing SVGs and JavaScript. The end results are very satisfying, though they can be a bit complex to implement. Here are a few worth checking out:
There’s more to borders than simply border, but if you want to animate a border you may have some trouble. The methods covered here will help, though none of them are a perfect solution. Which you choose will depend on your project’s requirements, so I’ve laid out a comparison table to help you decide.
My recommendation would be to use box-shadow, which has the best overall balance of ease-of-implementation, animation effect, performance and browser support.
Do you have another way of creating an animated border? Perhaps a clever way to utilize transforms for moving a border? Comment below or reach me on Twitter to share your solution to the challenge.
For your holiday gift shopping needs! These my picks for some of the most popular books out there on typography, with a tilt toward web typography. Plus a couple of bonus picks by our own Robin Rendle.
For your holiday gift shopping needs! These my picks for some of the most popular books out there on typography, with a tilt toward web typography. Plus a couple of bonus picks by our own Robin Rendle.
24 Ways, the advent calendar for web geeks, started up again this week. Throughout December they’ll be publishing a wide range of posts all about web design, CSS, and front-end development.
Chen Hui Jing has already written a great post about feature queries and Stephanie Drescher published a post today about a tool called sonarwhal which identifies accessibility, performance and security issues, just to name a few.