Introđź”—
My digital business card is inspired by this amazing 3d pokemon cards demo demo and also this twitter postwhich uses a similar effect.
I want to break down some of the techniques involved in a multi-part blog series. In this post we’ll focus on just the soft spotlight effect.
The effect is extremely subtle when you interact with these 3D cards. it mimics the reflection of a light source shining down on the card. This effect adds that extra level of realism to your 3D card.
Here’s the effect that we’re going to produce:
Let’s break this down, line by line.
The HTMLđź”—
<div class='card'>
<div class="card__softlight"></div>
</div>
The .card
is our container element which defines the element dimension and houses the inner element.
The spotlight effect is all achieved with the .card__softlight
class.
The CSSđź”—
.card {
position: relative;
height: 500px;
width: 900px;
background: grey;
}
.card__softlight {
position: absolute;
inset:0;
mix-blend-mode: soft-light;
background: radial-gradient(farthest-corner circle at var(--x, 10%) var(--y, 10%),
rgba(255, 255, 255, 0.8) 10%,
rgba(255, 255, 255, 0.65) 20%,
rgba(255, 255, 255, 0) 90%);
}
}
Some basic stuff in the card
class. As the children element will be absolute
positioned we need to remember to add position: relative
so all children elements are contained relative to this element.
Moving into .card__softlight
. we use inset: 0
to make this inner div “fill up” to the parent container. it’s the equivalent to :
top: 0;
right: 0;
bottom: 0;
left: 0;
There are a few things happening in the background
property. We’re using the radial-gradient
function to create a circular pattern with a subtle white glow effect.
if you want to see the radial gradient effect a bit clearer you can try swapping it for
background: radial-gradient( circle farthest-side at var(--x, 0%) var(--y, 10%), red 10%, green 20%, blue 80%);
Let’s take a deep dive into how the radial-gradient
function works.
MDN documents the radial-gradient
function like so:
radial-gradient( [ <ending-shape> || <size> ]? [ at <position> ]? , <color-stop-list> )
<ending-shape>
- can either be circle
or elipse
. elipse
is basically just a stretched circle to match the aspect ratio of the element it’s in. For our spotlight effect, we want to ensure the radial gradient is always a circle
.
Try swapping
circle
forelipse
to see how the radial gradient skews.
<size>
- has four options documented here
closest-side
closest-corner
farthest-side
farthest-corner
- default
This means we can omit farthest-corner
and it would still function the same. I’ve kept it for explicitness.
try swapping
farthest-corner
for one of the other options. Maybe you think a different one looks better. it’s completely subjective!
<position>
- defaults to center
but supports x
and y
positions. Note, for this argument we’re using var(--x, 10%)
and var(--y, 10%)
. These are CSS variables, the second argument in a CSS variable is the fallback value if either --x
or --y
has not been set yet. In the next section we will set --x
and --y
dynamically using javascript!
The final bit of magic is mix-blend-mode: soft-light
we will make heavy use of mix-blend-mode
throughout this tutorial series. This property will literally blend the radial background into the other elements. it is key to making the spotlight effect feel “softer”. Learn more about mix-blend-mode
on MDN
try removing this property and experimenting with other blend values e.g
multiply
,hard-light
ordifference
.
##\ The Javascript
With our CSS variables in place, we can now programmatically update the x
and y
positions of the radial gradient.
Firstly, we need to tap into the mousemove
event to get the position our our cursor when we hover over our card
. We can do so like this:
const card = document.querySelector(".card");
card.addEventListener("mousemove", (e) => {
console.log(e.clientX , e.clientY) //e.g 130 30
});
We have a problem though, the values returned from e.clientX
and e.clientY
are pixel positions of where the cursor is on the screen. Here is an example of the values that will be returned by e.clientX
and e.clientY
.
We want relative units within the card element itself like so:
To achieve this we need the following values:
- The size of the card element
- The position of the element relative to the viewport.
Fortunately, we have everything we need within Element.getBoundingClientRect()
. This method which exists on any HTML element returns an object that looks something like:
{
"x": 8,
"y": 8,
"width": 900,
"height": 500,
"top": 8,
"right": 908,
"bottom": 508,
"left": 8
}
We can now work out the relative unit of the cursor position within the .card
element with some basic maths!:
Here’s the logic we’ll need to write:
- Minus the card
left
position with the cursorx
position (this gives us a corrected position value of 0 to the max width of the card)- Divide this value with the
cardWidth
(this converts the value into a decimal range 0.0 - 1.0) - Multiply this value by 100 (this converts this figure into a percentage value)
- Divide this value with the
- Minus the card
top
position with the cursory
position (this gives us a corrected position value of 0 to the max height of the card)- Divide with the
cardHeight
(this converts the value into decimals between 0.0 - 1.0) - Multiply this value by 100 (this converts this figure into a percentage value)
- Divide with the
Let’s convert that pseudo logic into javascript:
const card = document.querySelector(".card");
const {
width: cardWidth,
height: cardHeight,
left: cardLeft,
top: cardTop
} = card.getBoundingClientRect();
card.addEventListener("mousemove", (e) => {
let X = (e.clientX - cardLeft) / cardWidth;
let Y = (e.clientY - cardTop) / cardHeight;
let cardXPercentage = `${X * 100}%`;
let cardYPercentage = `${Y * 100}%`;
console.log(cardXPercentage, cardYPercentage); // e.g "50%" "50%"
document.documentElement.style.setProperty("--x", cardXPercentage);
document.documentElement.style.setProperty("--y", cardYPercentage);
});
Note the last two lines is where the fun happens:
document.documentElement.style.setProperty("--x", cardXPercentage);
document.documentElement.style.setProperty("--y", cardYPercentage);
We set our percentage values into our CSS variables with CSSStyleDeclaration.setProperty()
. This is how as we move our cursor the radial gradient follows along 🙌.
In the next part we’ll explore how the tilt effect works,