Have you seen Netlify’s press page? It’s one of those places where you can snag a download of the company’s logo. I was looking for it this morning because I needed the logo to use as a featured image for a post here on CSS-Tricks.
Well, I noticed they have these pretty looking buttons to download the logo. They’re small and sharp. They grab attention but aren’t in the way.
They’re also interactive! Look at the way they expand and reveal the word “Download” on hover.
Nice, right?! I actually noticed that they looked a little off in Safari.
That made me curious about how they’re made. So, I recreated them here as a demo while cleaning up some of the spacing stuff:
See the Pen
Netlify Sliding Buttons by Geoff Graham (@geoffgraham)
on CodePen.
How’d they do it? The recipe really comes down to four ingredients:
- Using the
left
property to slide the “Download” label in and out of view - Using
padding
on the button’s hover state to create additional room for showing the “Download” label on hover - Declaring a 1:1
scale()
on the button’s hover state so all the content stays contained when things move around. - Specifiying a
transition
on the button’spadding
, thebackground-position
of the button icon and thetransform
property to make for a smooth animation between the button’s default and hover states.
Here’s what that looks like without all the presentation styles:
See the Pen
Style-less Netlify Sliding Buttons by Geoff Graham (@geoffgraham)
on CodePen.
If you’re having a tough time visualizing what’s happening, here’s an illustration showing how the “Download” label is hidden outside of the button (thanks to overflow: hidden
) and where it’s pushed into view on hover.
So, by putting negative left
values on the icon and the “Download” label, we’re pushing them out of view and then resetting those to positive values when the entire button is hovered.
/* Natural State */
.button {
background:
#f6bc00
url(data:image/svg+xml;base64,...)
no-repeat -12px center;
overflow: hidden;
}
.button span:nth-child(1) {
position: absolute;
left: -70px;
}
/* Hovered State */
.button:hover {
padding-left: 95px;
background-position: 5px center;
}
.button span:nth-child(1) {
position: absolute;
left: -70px;
}
Notice that leaving things in this state would let the button icon slide into view and create enough room for the “Download” label, but the label would actually float off the button on hover.
See the Pen
Style-less Netlify Sliding Buttons by Geoff Graham (@geoffgraham)
on CodePen.
That’s where adding a 1:1 scale on the button helps keep things in tact.
* Hovered State */
.button:hover {
padding-left: 95px;
background-position: 5px center;
transform: scale(1, 1);
}
Those padding values are magic numbers. They’ll be different for you based on the font, font-size, and other factors, so your mileage may vary.
The last core ingredient is the transition
property, which makes everything slide smoothly into place rather than letting them snap. It’s provides a much nicer experience.
/* Natural State */
.button {
background:
#f6bc00
url(data:image/svg+xml;base64,...)
no-repeat -12px center;
overflow: hidden;
transition: padding .2s ease, background-position .2s ease, transform .5s ease;
}
Toss in some little flourishes, like rounded corners and such, and you’ve got a pretty slick button.
See the Pen
Netlify Sliding Buttons by Geoff Graham (@geoffgraham)
on CodePen.
I didn’t know transform: scale has the same effect as position: relative. Thanks!
Surprisingly it has.
It feels a bit hacky though especially if you want to do scaling but you don’t want it to affect positioning.
I wonder why was transform needed and why position:relative wasn’t used, which is easier to understand and works the same.
Didn’t initially understand the effect of using transform: scale, thanks for mentioning about it :)
BTW, do you think there’s any particular reason for setting the transition (transform .5s ease)?
I’m still trying to make sense of what
scale
is doing here. Thanks for writing this up!Me too!
Scale is making sure it’s smoothly stretching out, I think.
As far as I understand, author uses
transform: scale(1, 1);
in order to create containing blockfor the absolute positioned
span:nth-child(1)
which is the word Download.It can be achieved by setting
position: relative;
on the parent element which in this case is<a class="button">
I don’t love this interaction overall. Is it just me or is it really disorientating?
Really nice interaction. I think you could save a few characters by dropping the
ease
on the transition (asease
is the default timing function).I think it is possible to achieve this with less magic numbers by animating the (max-)width of the element(s) that transition in and out. To try this out I forked your Codepen here: https://codepen.io/jperals/pen/jONZxWQ?editors=1100
What I did was to group the “download” text and the icon in one element and animate the (max-)width of this element. One problem I had was that it is not possible to animate width from 0 to “auto”, which is well explained in this other nice CSS Tricks article: https://css-tricks.com/using-css-transitions-auto-dimensions. But you can animate the max-width instead. As the article explains, this approach has the little drawback that you still have to hard-code some magic number as max-width, of course, and it will only work as long as the content is not larger than that, and also that the transition duration will be based on that maximum value. But still I’d say it’s a more flexible approach as it lets you have content of variable size (note how the button at the right has longer text). And we are also animating less things (actually just one). What do you think?
Thanks for the article, it made me learn a couple things, and hack a bit :)
Cool article. I did something similar with the buttons in a toolbar for an in-browser layout editor. Last year I had to create a custom landing page for a Dacor appliance promotion at work. I was able to recreate their cool JS button effect with just CSS, pseudo-elements, and a custom HTML data-attribute. Dacor no longer has the button effect on their site, but the CodePen below shows how I got it to work.