3D animations using CSS and Javascript - javascript

On Steam there is a game called Dota 2. They had this section of "cards" that showed the characters you play. When you hover over the card, there is a really cool animation. The card lifts up and pivots towards the mouse pointer.
Pictures are attached. The red dot represents where the mouse is hovering:
Bottom-left:
Center:
Left-center:
Currently, I have a basic version here: https://codepen.io/riza-khan/pen/mdyvEeg but the animation isn't working as intended.
I will also post the code at the end of this question should Codepen links not be allowed on Stackoverflow. Some of the code is redundant, but I kept it in there to gain more insight.
const container = document.querySelector('.container')
const card = document.querySelector('.card')
let x = document.querySelector('.x-axis')
let y = document.querySelector('.y-axis')
container.addEventListener('mousemove', (e) => {
let xCoords = e.offsetX
let yCoords = e.offsetY
x.innerHTML = `xCoords:${xCoords}`
y.innerHTML = `yCoords:${yCoords}`
card.style.transform = `rotateY(${yCoords}deg) rotateX(${xCoords}deg) scale(1.1)`
})
container.addEventListener('mouseout', (e) => {
card.style.transform = `rotateY(0deg) rotateX(0deg) scale(1)`
})
container.addEventListener('click', (e) => {
console.log(e)
})
body {
display: flex;
height: 90vh;
background: grey;
}
.x-axis,
.y-axis {
position: absolute;
}
.y-axis {
left:100px;
}
.container {
margin: auto;
}
.card {
height: 500px;
width: 300px;
border-radius: 10px;
overflow: hidden;
transition: all 1s ease;
box-shadow: 10px 8px 20px -20px black;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.container:hover {
.card {
transform-style: preserve-3d;
}
}
<p class="x-axis"></p>
<p class="y-axis"></p>
<div class="container">
<div class="card">
<img src="https://vignette.wikia.nocookie.net/wowwiki/images/d/d9/Illidan.png/revision/latest?cb=20140503134345" alt="">
</div>
</div>

This is what you're looking for. To give a 3D feel to an object, you have to use the CSS property perspective. Here's a working example (also available on CodePen):
const container = document.querySelector('.container')
const card = document.querySelector('.card')
const output = document.querySelector('.output')
let x = document.querySelector('.x-axis')
let y = document.querySelector('.y-axis')
container.addEventListener('mousemove', (e) => {
let xOffset = e.offsetX
let yOffset = e.offsetY
let cardHeight = card.clientHeight
let cardWidth = card.clientWidth
let heightCenter = Math.round(cardHeight / 2)
let widthCenter = Math.round(cardWidth / 2)
let rotateBaseValue = 20
let rotateXValue = (yOffset - heightCenter) / heightCenter * rotateBaseValue
let rotateYValue = (widthCenter - xOffset) / widthCenter * rotateBaseValue
card.style.transform = `scale(1.1) rotateX(${rotateXValue}deg) rotateY(${rotateYValue}deg)`
})
container.addEventListener('mouseout', (e) => {
card.style.transform = ''
})
html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100vw;
overflow: hidden;
}
body {
display: flex;
background: url(https://pbs.twimg.com/media/DySLFjlV4AEWf_F.jpg) no-repeat;
background-position: center bottom;
background-size: cover;
}
.container {
margin: auto;
perspective: 1000px;
}
.card {
height: 25vw;
width: 15vw;
border-radius: 10px;
overflow: hidden;
transition: all .25s linear;
}
.card img {
width: 100%;
height: 100%;
object-fit: cover;
}
.container:hover .card {
box-shadow: 10px 30px 50px -6px black;
}
<div class="container">
<div class="card">
<img src="https://66.media.tumblr.com/baf4a8fec55a6cb6fd7b18a7855998e4/tumblr_ply7xcI7pl1sjt61g_540.png" alt="Moonlight Cookie's Alluring Crescent Moon Costume">
</div>
</div>
EDIT
As per request, I will explain how I got the formula for rotateXValue and rotateYValue.
Before getting into that, you need to know what rotateX and rotateY do. rotateX rotates an item on the horizontal axis (x-axis) and rotateY rotates an item on the vertical axis (y-axis). Positive value on both rotateX and rotateY means that their movements are clockwise; negative means that their movements are counter-clockwise.
Try holding up a piece of paper. You'll notice that if you rotate the paper a little (doesn't matter how many degrees exactly) counter-clockwise on both x-axis and y-axis, you'll see that the paper seems exactly like when you are hovering on the top-right corner of the card. Try clockwise on both x-axis and y-axis, you'll see that the paper's pointing towards you like when you are hovering on the bottom-left corner of the card. Try all four combinations of different rotation directions.
After doing the above experiment, you can soon conclude that:
Top-left corner: rotateX counter-clockwise, rotateY clockwise
Top-right corner: rotateX and rotateY counter-clockwise
Bottom-left corner: rotateX and rotateY clockwise
Bottom-right corner: rotateX clockwise and rotateY counter-clockwise
Say that the maximum rotation is 15degrees. On the x-axis, value ranges from 15degrees to -15degrees (from left to right). On the y-axis, value ranges from -15degrees to 15degrees (from top to bottom). The card does not rotate when you're hovering in the middle of the card. Calculate the y-axis. The center is when the value is 0degree. Simply subtract the current y-offset with the center-offset and you'll get how much the distance is from the center-offset. Convert that to fraction relative to center-offset by dividing with the center-offset value. Multiply the fractional value to the maximum degrees to get how many degrees to rotate. Do the same with the x-axis (in this case, you need to invert the subtraction because the value ranges from positive to negative).
P.S.: This one was extremely fun to make. Thanks for the idea!

Related

Why element getBoundingClientRect() values condition is not working properly?

I have .square element when I move my mouse it should be moved with the mouse. Almost everything is good when I move my mouse normally but when I move my mouse fastly on the right and bottom side, Then the .square box goes to the down or right side and makes the scrollbar.
How can I make my code better than now OR How to fix the problem?
[NOTE]: Transition should have
let square = document.querySelector(".square");
document.addEventListener("mousemove", function (e) {
if (square !== null) {
let x = e.pageX;
let y = e.pageY;
square.setAttribute("style", `top: ${y}px; left: ${x}px`);
let getBottom = square.getBoundingClientRect().bottom;
let getRight = square.getBoundingClientRect().right;
if (getBottom > window.innerHeight) {
square.setAttribute("style", `bottom: 0; left: ${x}px`);
}
if (getRight > window.innerWidth) {
square.setAttribute("style", `top: ${y}px; right: 0`);
}
if (getRight > window.innerWidth && getBottom > window.innerHeight) {
square.setAttribute("style", `bottom: 0; right: 0`);
}
}
});
*,
*::before,
*::after{
box-sizing: border-box;
}
body{
margin: 0;
border: 1px solid blue;
}
.square {
position: absolute;
height: 50px;
width: 50px;
border: 5px solid red;
transition: .01s ease-in-out;
}
<div class="square"></div>
The problem is caused by defining a transition on the square element's position in CSS, combined with setting the position of the square before reading its current position:
Setting the position immediately to the mouse move position:
square.setAttribute("style", `top: ${y}px; left: ${x}px`);
can move the square out of the viewport and create scroll bars if the mouse is less than the length of the square's side away from the bottom or right of the browser window.
Reading the position of the square next:
let getBottom = square.getBoundingClientRect().bottom;
let getRight = square.getBoundingClientRect().right;
returns the updated position of the square if there is no transition, but with the transition returns where the square is now, before the transition starts. Since the square is not outside the window yet, none of the conditional statements using its old position detect that it will end up outside the window.
The easiest solution is to remove the CSS transition - keeping it at 0.01 second is less than monitor update refresh time and not particularly useful.
Getting the square's position once, before updating its position is another solution.
In either case it may be smoother to update the position of the square at most once, with the position where it transition to.
In this code used to find an answer, the html element's clientWidth and clientHeight properties are a special case of these properties and reflect the size of the view port excluding scroll bars. The transition timing is set to 0.05 seconds to avoid stroboscopic effect from screen refresh:
let square = document.querySelector(".square");
const HTML = document.firstElementChild;
document.addEventListener("mousemove", function (e) {
if (square !== null) {
let domRect = square.getBoundingClientRect()
let x = e.pageX;
let y = e.pageY;
x = Math.min( HTML.clientWidth - domRect.width, x);
y = Math.min( HTML.clientHeight - domRect.height, y);
square.style.top = `${y}px`;
square.style.left = `${x}px`;
//square.setAttribute("style", `top: ${y}px; left: ${x}px`);
}
});
*,
*::before,
*::after{
box-sizing: border-box;
}
body{
margin: 0;
border: 1px solid blue;
}
.square {
position: absolute;
height: 50px;
width: 50px;
border: 5px solid red;
transition: .05s ease-in-out;
}
<div class="square"></div>

Why does the circle in this code snippet lag behind the actual mouse cursor?

I am new to JavaScript and taking JavaScript Training course. The js code is supposed to render a circle at the mouse pointer location and listen for any changes.
const AREA = document.body;
const CIRCLE = document.querySelector('.circle');
function mouseCoordinates(e) {
var horizontalPosition = e.clientX - 26;
var verticalPosition= e.clientY - 26;
// Set horizontal and vertical position.
CIRCLE.style.left = horizontalPosition + 'px';
CIRCLE.style.top = verticalPosition + 'px';
}
AREA.addEventListener('mousemove', mouseCoordinates, false);
body {
width: 100vw;
height: 100vh;
}
.circle {
position: absolute;
top: 1px;
width: 50px;
height: 50px;
color: transparent;
border: 2px solid red;
border-radius: 50%;
}
<div class="circle"></div>
Initially the mouse pointer stays at the center of the circle, but when I am slightly increasing the speed of mouse movement the circle seems to "lag" behind.
Why does this happen?
Is this related to the performance of my system?
Isn't the listener supposed to run as and when the mouse moves and draw the circle exactly over the current position of the mouse?

Image clip centre

I want my image to be clipped in the centre and be able to move around left right up down while being on cover to fit the whole screen
The user should be able to see only a certain portion of the image and be able to move around just like the link below but his viewpoint is the little rectangle in a fit to screen perspective
What i get so far is just the clipped upleft of my image
In case i am not clear what I am trying to achieve this effect but the user can't see move than the square
https://www.w3schools.com/howto/howto_js_image_zoom.asp
I will soon close this ticket if you happen to stumble on this check out this q I believe I am as as clear as I can here
How to clip your image freely
<style>
img {
background-position: cover;
position: absolute;
clip:rect(0px,500px,500px,0px);
}
.image1 {
background: url(images/bg.jpg) no-repeat center center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
</style>
</head>
<body>
<div class='clipper-div'>
<img class='image1' src='office.gif'/>
</div>
The kind you were looking for is an inset clipping:
clip-path: inset(top bottom left right);
You can listen to the mouse move event to update the clipping. In the example below, I used CSS custom properties I added to the clipper-element style definition.
These custom properties are used as CSS variables for the clipping definition.
// Globals variables (we could store them into an object,
// which would be a cleaner way
var clipperDiv = document.getElementById("clipper-div");
var hoveringClippedImg = document.getElementById("hovering-clipped");
var imgBoundingRect = hoveringClippedImg.getBoundingClientRect();
var clippingSize = 40;
// Surrouding DIV element mouse move event callback
clipperDiv.onmousemove = clipHoveredArea;
// Update image clipping
function clipHoveredArea(e) {
// First step: getting clipping coordinates from mouse position
var pos = getMousePos(e);
var top = (pos.y - clippingSize / 2);
var bottom = (imgBoundingRect.height - pos.y - (clippingSize / 2));
var left = (pos.x - clippingSize / 2);
var right = (imgBoundingRect.width - pos.x - clippingSize / 2);
// Second step: CSS custom properties
hoveringClippedImg.style.setProperty("--top", top + "px");
hoveringClippedImg.style.setProperty("--bottom", bottom + "px");
hoveringClippedImg.style.setProperty("--left", left + "px");
hoveringClippedImg.style.setProperty("--right", right + "px");
};
// Get mouse position relative to an element
// Source: //stackoverflow.com/a/42111623/4375327
function getMousePos(e) {
// e = Mouse click event.
var rect = e.currentTarget.getBoundingClientRect();
var x = e.clientX - Math.round(rect.left);
var y = e.clientY - Math.round(rect.top);
return {
x: x,
y: y
};
}
#clipper-div {
border: 2px solid black;
width: 200px;
height: 200px;
}
#hovering-clipped {
padding: 0;
margin: 0;
width: 200px;
height: 200px;
clip-path: inset(var(--top) var(--right) var(--bottom) var(--left));
--top: 0px;
--right: 0px;
--bottom: 0px;
--left: 0px;
cursor: crosshair;
}
<div id='clipper-div'>
<img id="hovering-clipped"
src="//placehold.it/200x200/d0d8f8/000000" />
</div>
Note: I used Clippy. It's a handy tool to design the clipping you want.

How to adjust the mouse position after rotating an object?

I am trying to create an eye that follows cursor movement.
I got the horizontal and vertical coordinate of the mouse and the browser width and height.
Everything works perfectly. Except that I used rotate(45 deg) on the design of the eye so now the ball is not moving in the right position.
I was thinking about a math equation that finds the distance between the old and new coords, but I am not sure how to implement it.
Here is the full code:
https://jsfiddle.net/Mr_MeS/3ym6kuec/3/
so this is the .eye where its rotated
.eye {
width: 37.5px;
height: 37.5px;
background: white;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
border-radius: 75% 0;
overflow: hidden;
cursor: pointer;
}
.ball {
width: 7.5px;
height: 7.5px;
background: #222f3e;
border-radius: 50%;
border: 5px solid #576574;
position: relative;
top: 50%;
left: 50%;
}
and here is the JS that does the work and needs to be edited.
var balls = document.getElementsByClassName("ball");
document.onmousemove = function () {
var x = event.clientX * 100 / window.innerWidth + "%";
var y = event.clientY * 100 / window.innerHeight + "%";
//event.clientX => get the horizontal coordinate of the mouse
//event.clientY => get the Vertical coordinate of the mouse
//window.innerWidth => get the browser width
//window.innerHeight => get the browser height
for (var i = 0; i < 2; i++) {
balls[0].style.left = x;
balls[0].style.top = y;
balls[0].style.transform = "translate(-" + x + ",-" + y + ")";
}
}
Now, if I remove the rotation from the .eye, it works perfectly, expect that the whole shape doesn't look to be in position.
If I keep the 45deg rotation, the shape is good, but the ball moves wrongly.
You could try to put the eye-background (the white part that needs to rotate 45 degrees) into a div (or pseudo-element) that's inside the .eye element. In that way you don't need to rotate the container element, so the coordination of the ball element stays the same.
Another point, why are you using that for-loop? I think running the code once will be sufficient :)
EDIT: I've been playing around with your example a bit and fixed it. What happens is that if you rotate an element, the direction in which things will transform (and top/left positioning) will also change. So moving the element 10px to the left, will go 10px to the left, under a 45 degree angle, because it's rotated 45 degrees.
What I did now was to put an element (.inner) inside the eye div, which I gave a counter-rotation of -45 degrees. In this way, the container element of the ball has the correct orientation again, which fixes the problem: https://jsfiddle.net/bxprjvgL/
HTML:
<div class="eye">
<div class="inner">
<div class="shut"><span></span></div>
<div class="ball"></div>
</div>
</div>
CSS:
.inner {
width: 100%;
height: 100%;
transform: rotate(-45deg);
}

move img with mousemove

I want to be able to move an img around within its container once the image is zoomed in, because as you can see once you click the image it becomes too big and you can't see the whole image. Also how can I make the image goes back to normal once it's not being hovered? thanks in advance.
// Zoom in/out clothing img
$('.image').click(function() {
$(this).toggleClass('normal-zoom zoom-in');
});
.container {
width: 800px;
margin: 0 auto;
border: 2px solid black;
display: flex;
}
.img-wrapper {
margin: 10px;
overflow: hidden;
}
.image {
width: 100%;
height: 100%;
}
.text {
width: 40%;
padding: 20px;
}
.normal-zoom {
transform: scale(1);
cursor: zoom-in;
transition: all 250ms;
}
.zoom-in {
transform: scale(1.6);
cursor: zoom-out;
transition: all 250ms;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="img-wrapper">
<img src="https://static1.squarespace.com/static/560c458be4b0af26f729d191/560c5de0e4b083d9c365515f/560d53d4e4b03b1013fd40de/1443714010032/lauren-winter-wide-pant-natural_0178.jpg?format=750w" class="image normal-zoom">
</div>
<p class="text">Kept in sent gave feel will oh it we. Has pleasure procured men laughing shutters nay. Old insipidity motionless continuing law shy partiality. Depending acuteness dependent eat use dejection. Unpleasing astonished discovered not nor shy. Morning hearted now met yet beloved evening. Has and upon his last here must. Cottage out enabled was entered greatly prevent message. No procured unlocked an likewise. Dear but what she been over gay felt body. Six principles advantages and use entreaties decisively. Eat met has dwelling unpacked see whatever followed. Court in of leave again as am. Greater sixteen to forming colonel no on be. So an advice hardly barton. He be turned sudden engage manner spirit.</p>
</div>
Since you're using transform: scale() for the zoom effect it's faster and more correct to modify transform-origin to change the center point of the zoom effect on mousemove:
// Zoom in/out clothing img
$('.image').click(function() {
$(this).toggleClass('normal-zoom zoom-in');
});
$('.image').on('mousemove', function(event) {
// This gives you the position of the image on the page
var bbox = event.target.getBoundingClientRect();
// Then we measure how far into the image the mouse is in both x and y directions
var mouseX = event.clientX - bbox.left;
var mouseY = event.clientY - bbox.top;
// Then work out how far through the image as a percentage the mouse is
var xPercent = (mouseX / bbox.width) * 100;
var yPercent = (mouseY / bbox.height) * 100;
// Then we change the `transform-origin` css property on the image to center the zoom effect on the mouse position
//event.target.style.transformOrigin = xPercent + '% ' + yPercent + '%';
// It's a bit clearer in jQuery:
$(this).css('transform-origin', (xPercent+'% ' + yPercent+ '%') );
// We add the '%' units to make sure the string looks exactly like the css declaration it becomes.
});
// If you want it to automatically trigger on hover
$('.image').on('mouseenter', function() {
$(this).addClass('zoom-in');
$(this).removeClass('normal-zoom');
});
// and stop when not hovering
$('.image').on('mouseleave', function() {
$(this).addClass('normal-zoom');
$(this).removeClass('zoom-in');
});
.container {
width: 800px;
margin: 0 auto;
border: 2px solid black;
display: flex;
}
.img-wrapper {
margin: 10px;
overflow: hidden;
}
.image {
width: 100%;
height: 100%;
}
.text {
width: 40%;
padding: 20px;
}
.normal-zoom {
transform: scale(1);
cursor: zoom-in;
transition: transform 250ms;
}
.zoom-in {
transform: scale(1.6);
cursor: zoom-out;
transition: transform 250ms;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="img-wrapper">
<img src="https://static1.squarespace.com/static/560c458be4b0af26f729d191/560c5de0e4b083d9c365515f/560d53d4e4b03b1013fd40de/1443714010032/lauren-winter-wide-pant-natural_0178.jpg?format=750w" class="image normal-zoom">
</div>
<p class="text">Kept in sent gave feel will oh it we. Has pleasure procured men laughing shutters nay. Old insipidity motionless continuing law shy partiality. Depending acuteness dependent eat use dejection. Unpleasing astonished discovered not nor shy. Morning hearted now met yet beloved evening. Has and upon his last here must. Cottage out enabled was entered greatly prevent message. No procured unlocked an likewise. Dear but what she been over gay felt body. Six principles advantages and use entreaties decisively. Eat met has dwelling unpacked see whatever followed. Court in of leave again as am. Greater sixteen to forming colonel no on be. So an advice hardly barton. He be turned sudden engage manner spirit.</p>
</div>
You can use the mousemove event listener on the image with class .zoom-in to change the left and top CSS params. Make sure to set position:relative; on the image.
Example:
$(document).on('mousemove', '.zoom-in', function( event ) {
$(".text").text(event.pageX + ", " + event.pageY);
var positionLeft = event.pageX - $(this).width()/2;
var positionTop = event.pageY - $(this).height()/2;
$(this).css({'left': positionLeft, 'top': positionTop});
});
Here is a fiddle.

Categories

Resources