Growing Box Hover in CSS
Result
Hover over the card to see the effect:
Neither is this text!
Introduction
3 years ago I was tasked at work with figuring out a way to “grow” a div
in all directions by a few pixels without shifting the content inside the element and without affecting the content outside. There are various solutions already to be found on the web, all seem to cause various graphical problems on certain browsers or make use of CSS rules that limit how the element can be used. We wanted free rein on where and how the element can be used.
How It’s Done
After playing around (for many many hours) with pretty much every single CSS rule under the sun related to spacing, I came up with a solution. My solution puts a smaller “frame” or “window” div
over the content and uses overflow: hidden
to hide the overflowing content. This frame is then expanded in all directions. To get this to work, some tricks have to be used. Check out the HTML and CSS with explanations directly included.
<!-- grow-main counteracts grow-expansion -->
<div class="grow-main">
<!-- grow-expansion represents the "frame" that hides the
content not visible in that frame and does the growing -->
<div class="grow-expansion">
<!-- grow-content holds all your content -->
<div class="grow-content">
...</div>
</div>
</div>
/* Use CSS variables for growth amount and growth speed */
:root {
--growth: 15px;
--time: 0.2s;
}
.grow-main {
/* Counteract the offset in grow-expansion
This way your elements still go where you expect them to go */
top: calc(-1 * var(--growth));
left: calc(-1 * var(--growth));
position: relative;
}
.grow-expansion {
position: relative;
/* Offset so we can grow to the left and to the top too */
top: var(--growth);
left: var(--growth);
/* Required, since we grow using padding */
box-sizing: content-box;
/* Hide the content not shown in this "frame" */
overflow: hidden;
/* Make sure the frame covers all of the content */
width: 100%;
/* Optional box-shadow to make your div "card-like" */
box-shadow:
rgba(0, 0, 0, 0.13) 0px 2px 5px 1px,
rgba(0, 0, 0, 0.18) 0px 1px 2px 0px;
/* Transition values */
margin-bottom: 0px;
padding: 0px;
transition:
var(--time),
padding top var(--time),
left var(--time),
var(--time);
margin-bottom
}
.grow-expansion:hover {
/* Grow the frame into all directions with padding */
padding: var(--growth);
/* Simultaneously, remove the offset
to simulate growing to top and left */
top: 0px;
left: 0px;
/* This ensures that the layout outside the element is not affected
As the element expands up and down (both by --growth),
"suck in" space from the bottom (moving bottom content closer)
at the same rate the element is expanding */
margin-bottom: calc(-2 * var(--growth));
}
.grow-content {
/* This is needed to fit the content into the expanded frame.
Negative margins are still a bit magical to me, so I'm not 100% sure
how I came up with this, but it works */
margin: calc(-1 * var(--growth));
/* If your element expands over something else, the content might
overlap, so setting a base background color fixes that */
background-color: white;
}
Additional Notes
Modifications
You can adjust the shape and size of the element to your liking and play around with the amount and speed of the transition. Maybe this could even be modified to work as a circular shape.
Benefits
You do not have to position: absolute
this element. It can be part of the document flow without affecting it, making this a purely visual effect.
The element takes up as much space as the unexpanded version does. This means that there is no “phantom space” being wasted. However, you have to manually add spacing should you not want your box to overlap other content.
Any content can go inside the box, you are not limited to text and images.
The element is entirely self-contained and modular. You can make a React component out of it.
Problems
There might be some graphical issues on certain browsers. I have not tested it too extensively (besides the common browsers).
Also, keep in mind, the state of my knowledge on this is 3 years old. You can pretend like this blog post was written back then. Maybe by now, there is a cleaner and easier implementation.