3D Floating Card Tracked by Mouse Movement (Angular)


From the GIF image you can see what kind of effect we are going for. So let’s take a look at how to achieve this with Angular. I have generated an Angular project and for the purpose of this article/tutorial we will implement this effect in the app component.

So let’s create the HTML page first

1
2
3
4
5
6
7
<div class='wrapper' (mousemove)='onMouseMove($event)'>
<div class="card" #card>
<div class="card-content">
<p class="info">Photo by Doug Maloney on Unsplash</p>
</div>
</div>
</div>

Here we have a wrapper which we are going to extend to full screen later with css, we have an eventListener (mousemove) and our onMouseMove($event) method that will grab the event $event. Than we have a div with a class card and a template reference variable #card. Next we have the card content with a class card-content and a paragraph with a class info.

For our next step we can focus on the css

1
2
3
4
5
6
7
.wrapper {
background: #2d809c;
-webkit-perspective: 500px;
perspective: 500px;
display: flex;
height: 100vh;
}

The Wrapper, here we add a background color, set the display to flex, we set the height of the wrapper to 100vh of viewport height (basicly making it full-size) and perspective of 500px defining how far the card is away from the user (we need this for later on when we mess with the transform property).
Now let’s make the card

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.card {
background-image: url("../assets/images/doug-maloney-767820-unsplash.jpg");
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
padding: 30px;
border-radius: 10px;
width: 250px;
height: 250px;
margin: auto;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
box-shadow: 0 0 5px rgba(20, 20, 20, .6);
}

I have downloaded an image from Unsplash and added it to /assets/images/ and i have set that as background of our card, next there is some padding and some border radius and width and height to make it a bit rounded, added in slight shadow and some transform-style (specifying how nested elements are rendered in 3D space). This property is supposed to be used together with transform property so in our typscript file later, you can see how to add this aditinal poperty that will have dynamic values based on the pointer position.

Card after

1
2
3
4
5
6
7
8
9
10
.card:after {
content: " ";
position: absolute;
width: 100%;
height: 10px;
border-radius: 70%;
left: 0;
bottom: -50px;
box-shadow: 0px 25px 15px #1a18184d;
}

Of corse to make it look believable that the card is floating we add a bit of shade under our card nothing special here.

Card content

1
2
3
4
5
6
.card-content {
margin: auto;
text-align: center;
bottom: 20px;
position: absolute;
}

Same here, nothing special just positioning the content on the bottom of the card.

Info

1
2
3
4
5
6
7
8
.info {
display: block;
-webkit-transform: translateZ(60px);
transform: translateZ(60px);
font-style: italic;
font-weight: bold;
color:white;
}

Now here for the transform property i set the translateZ(60px) to give the text that perspective that it’s sort of floating in front of the card in about 60px apart.

That’s all about our CSS, now it’s time to move on with app.component.ts

First thing to do is to import ViewChild, ElementRef and Renderer2

1
import { Component,ViewChild,ElementRef,Renderer2} from '@angular/core';

Next pass in Renderer2 to the constructor

1
constructor(private renderer:Renderer2){}

Grab a reference to the HTML card element

1
@ViewChild('card') card: ElementRef;

And build onMouseMove method

1
2
3
4
5
onMouseMove(event) {
let x = -(window.screen.width / 2 - event.screenX) / 10;
let y = (window.screen.height / 2 - event.screenY) / 7;
this.renderer.setStyle(this.card.nativeElement,'transform',`rotateY(${x}deg) rotateX(${y}deg)`);
}

So here we have x and y values generated from the window width and height, divided by 2, minus the position of the mouse pointer which we grab from passing in the $event, you can play with these values depending on how you want the card to flow in your app. I wanted it to be smooth and not go over 180 degrees when we move the pointer from one side to the other that’s why i have divided it by 10 on the X and 7 on the Y axis.
Next we grab the renderer, call setStyle and pass in the card element this.card.nativeElement than the style transform and the value rotateY(${x}deg) rotateX(${y}deg).
So that’s it guys, hope this was fun and you can find a use in your projects for this, you can find the full code on my github .
Thanks and I’ll cya in the next one.

Share