How to Create Direction-Aware CSS-Only Hover Effects
Overview
A couple of tutorials for direction-aware CSS-only hover effects.
Required
These tutorials are intended for people who have at least a basic knowledge of HTML and are proficient to advanced in CSS/SASS.
Disclaimers
These effects do not work in older browsers — you will need a browser capable of rendering 3D transforms. I’ve tested in the latest versions of Chrome, Firefox, and Safari.
I also use the amazing AutoPrefixer instead of vendor prefixes. If you’re using CodePen, make sure to go to your pen’s settings, click on CSS, and select AutoPrefixer.
Parallax Hover Effect
My design partner and I stumbled across the Kikk Festival homepage back in 2015, which featured these awesome parallax hover cards. We loved the look and wanted to recreate it in a project, but due to the nature of the codebase, wouldn’t be able to use Javascript to get it right. Undeterred, I began searching for solutions that were CSS-only, and stumbled across something interesting.
The All-Powerful Tilde
One of the most fascinating CSS selectors for me is the ~
tilde, which selects the nearest element that matches the selector and is preceeded by the first. For example, ul ~ p
selects the p elements in
<div>
<ul></ul>
<ol></ol>
<p></p>
<p></p>
</div>
I realized that I could use this to create direction-aware hover effects for virtually anything!
Structure
The structure for these links are fairly straightforward — a container with multiple <a>
elements and one container for the card content. All href
s should contain the same URL.
<div class="container">
<a href="http://gabriellew.ee"></a>
<a href="http://gabriellew.ee"></a>
<a href="http://gabriellew.ee"></a>
<a href="http://gabriellew.ee"></a>
<div class="card">
<h1>Content</h1>
</div>
</div>
We first add some basic SASS/CSS to position the links over each corner of the card. You can do more links, but for simplicity’s sake, we’ll stick to four. We’ll also be using Haml to speed up the process.
Since we don’t have Javascript to alert us to the direction of the mouse movement or the location of the mouse, the <a>
elements act to divide the card up into detection areas - if you move your mouse in from above <a>
#1, for example, your mouse would enter the detection area for #1 before you'd enter the detection area for #3 or #4.
Hovers
The next step is to get each link event to affect the actual card element. This is where the tilde comes in.
a {
&:hover, &:focus {
~ .card {
// some CSS here
}
}
}
This affects the card when you hover over any of the links, as opposed to the +
selector which would only select the immediate sibling.
Next, we’ll add the individual link-specific hovers. I changed the link backgrounds to borders so you can see the effect more easily.
Transforms
Now that we know that each corner is being detected correctly, we’ll add in the transforms that will tilt the card backwards to each corner. The first thing to do is add perspective to the container. I usually use something within the range of 1000px
or so - just enough to show a subtle depth.
For this kind of parallax, the most important property is not the actual transform, but the transform-origin for the card. It needs to change for each detection area, and be on the opposite corner of the current detection area.
For example, if you hover over detection area #1 (top left), the transform-origin needs to be bottom right
, the opposite corner. The idea is to simulate the detected corner moving backwards while the rest of the corners move forward.
Finishing Touches
To complete the parallax, we also add a border inside the card that translates forwards when you hover over a link. You can also apply this technique to other elements inside the card.
// styling for the border
.card {
position: relative;
.border {
transform-origin: center center;
position: absolute;
top: 12px;
left: 12px;
width: calc(100% - 24px);
height: calc(100% - 24px);
border: 2px solid white;
}
}
// parallax behavior
.container {
a {
&:hover, &:focus {
~ .card {
.border {
transform: translateZ(24px);
}
}
}
// pushes the border backwards when you click
&:active {
~ .card {
.border {
transform: none;
}
}
}
}
}
The last two steps are to (1) add a transition to the card element so that moving from corner to corner is smooth and (2) remove styling from the links so that they’re invisible. I like to set the opacity to 0
to prevent any mishaps. I also translate the current link forward to prevent overlap from the card rotations.
Result
Below is a version of my completed parallax hover pen — toggle the checkbox to see where my links are. I decided to add in more links to refine the parallax further.
Cube Link Hover Effect
Another website that makes impressive use of direction-aware hover effects is Adult Swim Singles 2016. If you click on the calendar-like icon near the top left corner, you are taken to a 3d calendar where each day is a cube with direction-aware rotations. What an great idea!
Immediately I knew that I could do a similar effect using tildes. I already had a cube shape that I could easily modify for the links as well as the base for the parallax hover.
Base
The main difference between the cube links and the parallax hover is the format of the detection areas. Instead of using corners, we use entire sides/edges as detection areas — four triangles instead of four rectangles.
With the above in mind, we can start building the cube link base. We’ll need to create an outer container, four triangle-shaped links that form a square, and one inner container with six elements — four empty, two with content. My preferred order for the six is: top, bottom, left, right, front, back.
<div class="link">
<a href="http://codepen.io/gabriellewee/"></a>
<a href="http://codepen.io/gabriellewee/"></a>
<a href="http://codepen.io/gabriellewee/"></a>
<a href="http://codepen.io/gabriellewee/"></a>
<div class="cube">
<div></div>
<div></div>
<div></div>
<div></div>
<div>Front content</div>
<div>Back content</div>
</div>
</div>
For the triangle shaped links, you can use Clippy to get triangles in the correct shape. You’ll also need SVG clip-paths for Firefox compatibility. In addition, the links must have a positive translateZ (moved forward) — otherwise, they’ll get hidden behind the cube.
We use a 12px variable to define widths, heights, and transforms for the cube. To differentiate each side of the cube, we use varying shades of one color to simulate lighting, with the back side as pure white.
We’ve rotated the cube slightly so that you can see that each side is positioned correctly. If you take a look at the transforms, you’ll notice that all the cube sides have negative translateZ — meaning that each side of the cube besides the front one are pushed backwards.
Because of the nature of 3D transforms, this means that unless you translate the entire cube forward, some of it may get cut off behind the background. You can build the cube forwards to prevent this problem, but I already had a cube base so I left it alone for the most part. Let me know in the comments if you have trouble understanding this part or just building a cube in general.
Hovers
The next step is to add in the rotations on hover. You can style this any way you like, but I found that the most realistic rotate was away from the mouse interaction. So if the mouse enters from the left side, the cube should rotate from left to right. Each rotation should be 180deg
/0.5turn
or the negative equivalent. We also add in the transitions early to make sure that cube is rotating correctly.
Polishing
There are two things that need to be polished:
- The bottom triangle causes the back text to show up upside-down.
- The hovers are extremely trigger-happy: moving from one triangle to another causes unnecessary rotations.
For the upside-down text, the simplest way to solve this problem is to have the back face rotate to the right position without any transition so that the change happens before the main rotation. For some reason, changing the behavior of the back face for the bottom triangle also changed it for the top triangle, so we need to add the extra rotate for both hovers.
The trigger-happy behavior was a little more difficult to fix. The way to solve it is to have the current link take up the entire space from the other three (full width/height, remove clip-path, move forward), but that also gets rid of the interesting corner rotations when you move your mouse around quickly over the cube. I finally decided on inserting a transition delay so that the fix doesn’t kick in until you’ve had your cursor on the cube for more than one second. The delay doesn’t get rid of all the problems, but it alleviates most of them.
Result
The pen below is my completed published pen with colors and icons added. While the concepts were more difficult in this project, I finished it more quickly than the parallax hover because of the concepts I learned while making the parallax hover.
Conclusion
There are so many interesting hover effects you can do with the combination of tildes, transforms, and transitions. My design partner suggested a fishbowl where the fish swim in different directions based on your cursor direction. Others I thought of include water ripple effects, image tilt-shifts, and motion blur animations. Let me know if you try this technique for your own experiments!
Originally published at //codepen.io/gabriellewee/post/how-to-create-direction-aware-css-only-hover-effects. Edited 07/13/2021 to fix embeds.