/    Sign up×
Community /Pin to ProfileBookmark

Tilt effect does not work

Hi guys, I wrote this function but for some reason, it doesn’t work and I also do not get an error.
Do you know why?
https://codepen.io/raul-rogojan/pen/PomyNPB?editors=1010

to post a comment
JavaScript

33 Comments(s)

Copy linkTweet thisAlerts:
@SempervivumAug 05.2021 — Bracketing in the value for transform is wrong, this works for me:
``<i>
</i> // card.style.transform =
perspective(1000px) rotateX(${rotateX}deg rotateY(${rotateY}deg));
card.style.transform =
perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg);<i>
</i>
``
Copy linkTweet thisAlerts:
@codyhillauthorAug 06.2021 — @Sempervivum#1635104 I spent too much time trying to fit it and I could not see that xd

for some reason in code pen work perfectly fine, but in my project is work different xd

And it's the same exact code...
Copy linkTweet thisAlerts:
@codyhillauthorAug 06.2021 — @Sempervivum#1635104 I have found a problem. In code pen card.offsetTop is 70 and in my project is 991... I don't know why.

I tried changing the offsetTop with card.style.top but it has the same effect only in reverse.

Is it because the section has a margin?

Also for some reason, the transition doesn't work.
Copy linkTweet thisAlerts:
@SempervivumAug 06.2021 — MDN says about offsetTop:
>The HTMLElement.offsetTop read-only property returns the distance of the outer border of the current element relative to the inner border of the top of the offsetParent node.


https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop

I suspect that in your project there is some content above the card that increases offsetTop.
Copy linkTweet thisAlerts:
@codyhillauthorAug 06.2021 — @Sempervivum#1635138 Yeah, I know... and yeah there is.

Any fix?

As I said I tried style.top and i got the same resolts.
Copy linkTweet thisAlerts:
@SempervivumAug 06.2021 — The reason for this issue is that the mouse position is relative to the viewport while card.offsetLeft and card.offsetTop are related to the parent element. Thus you need to get the position of the card related to the viewport. This is discussed here and the solution given is using getBoundingClientRect:

https://stackoverflow.com/questions/1350581/how-to-get-an-elements-top-position-relative-to-the-browsers-viewport

This javascript works for me:
``<i>
</i> const cardTilt = () =&gt; {
const cards = document.querySelectorAll(".card");

cards.forEach((card, event) =&gt; {
const cardMouseMove = (event) =&gt; {
const boundingRect = card.getBoundingClientRect();
const cardWidth = card.offsetWidth;
const cardHeight = card.offsetHeight;
const centerX = boundingRect.left + cardWidth / 2;
const centerY = boundingRect.top + cardHeight / 2;
const mouseX = event.clientX - centerX;
const mouseY = event.clientY - centerY;
const rotateX = (+1 * 25 * mouseY) / (cardHeight / 2);
const rotateY = (-1 * 25 * mouseX) / (cardWidth / 2);

// card.style.transform =
perspective(1000px) rotateX(${rotateX}deg rotateY(${rotateY}deg));
card.style.transform =
perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg);
};
card.addEventListener("mousemove", cardMouseMove);
});
};
cardTilt();<i>
</i>
``
Copy linkTweet thisAlerts:
@codyhillauthorAug 06.2021 — @Sempervivum#1635143 Yep, works just fine. I forgot about getBoundingClientRect and I did not know how ofssetTop works. Thank you very much
Copy linkTweet thisAlerts:
@codyhillauthorAug 06.2021 — @Sempervivum#1635143 One last question.

Is all this performance-heavy? I added 4 cards and it's being laggy. First, the car is jumping around after the mouse and after a while, it works fine.
Copy linkTweet thisAlerts:
@SempervivumAug 06.2021 — Generally spoken animations are performance-heavy indeed, particularly when 3D. It highly depends on the power of the device the browser is running on. I myself coded a 3D animation which was running smoothly on my desktop PC (medium power, no high-end one) but other users reported that it was laggy on mobiles.

Regarding your cards: My first thought was that you might limit the animation to the card the mouse is on currently. However when I test it this is the case already, only one card is moving.
Copy linkTweet thisAlerts:
@codyhillauthorAug 06.2021 — @Sempervivum#1635175 I have added this function both on mouse enter and leave.
``<i>
</i> const setTransition = () =&gt; {
card.style.transition =
"transform 300ms cubic-bezier(.03, .98, .52, .99)";

clearTimeout(card.transitionTimeoutId);
card.transitionTimeoutId = setTimeout(() =&gt; {
card.style.transition = "";
}, 300);
};<i>
</i>
``

What I noticed is that after the transition is removed, the cards work fine, but in that period where the transition is still on the card, the animation is laggy. At first, I had the timeout set to 3000 which made the lag significant. Now I reduced it at 200 / 300 and it feels a lot better. The problem is that now the card is jumping into position once the mouse enters.
Copy linkTweet thisAlerts:
@SempervivumAug 06.2021 — Such methods for improving performance are commonly used for the scroll event but should be applicable to mouse move as well. Several options are introduced here:

https://joji.me/en-us/blog/how-to-develop-high-performance-onscroll-event/
Copy linkTweet thisAlerts:
@codyhillauthorAug 06.2021 — @Sempervivum#1635180 The one with the revers animation
``<i>
</i> const stepDelay = 50,
aniDuration = 300,
aniEasing = 'ease-in-out',
colOdd = '#9f9fed',
colEven = '#736ced',
colBase = 'white';
const logo = document.querySelector('#svg-logo');
const paths = document.querySelectorAll('#svg-logo path');
const aniKfEven = [
{ fill: colEven }
];
const aniKfOdd = [
{ fill: colOdd }
];
const aniKfRev = [
{ fill: colBase }
];

// Animation forwards: Colorize paths one by one
function makeAnimation() {
for (let i = 0; i &lt; paths.length; i++) {
const aniKf = i % 2 ? aniKfOdd : aniKfEven;
const aniOpt = {
delay: i * stepDelay,
duration: aniDuration,
iterations: 1,
fill: 'forwards',
direction: 'normal',
easing: aniEasing
};
ani = paths[paths.length - i - 1].animate(aniKf, aniOpt);
ani.play();
}
}

// Animation backwards: Remove colors from paths one by one
function makeAnimationRev() {
for (let i = 0; i &lt; paths.length; i++) {
const aniKf = aniKfRev;
const aniOpt = {
delay: i * stepDelay,
duration: aniDuration,
iterations: 1,
fill: 'forwards',
direction: 'normal',
easing: aniEasing
};
ani = paths[i].animate(aniKf, aniOpt);
ani.play();
}
}
logo.addEventListener('mouseover', makeAnimation);
logo.addEventListener('mouseout', makeAnimationRev);<i>
</i>
``
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — I see, forgot about this one. And you intend to animate two logos, which are equal, at the same time?
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635213 I want one to use as a preloader and one on the website itself. I would like to change the this
``<i>
</i> const logo = document.querySelector('#svg-logo');
const paths = document.querySelectorAll('#svg-logo path');<i>
</i>
``

also the durations and colors. Also the preloader I would like to run with a setInterval function. But I have a feeling I have to rewrite the function.

I tried adding parameters but that doesn't work. Or at least I don't know how to do it. I know it's possible but I can't remember.
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — I see, no synchronized animations but you want to use the same functions for both, the preloader and on the website itself? Using parameters you are on the right track: Handing over the logo and other values as parameters to both functions should be appropriate.
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635215 I reused this
``<i>
</i> const stepDelay = 50,
aniDuration = 300,
aniEasing = 'ease-in-out',
colOdd = '#9f9fed',
colEven = '#736ced',
colBase = 'white';
const logo = document.querySelector('#svg-logo');
const paths = document.querySelectorAll('#svg-logo path');
const aniKfEven = [
{ fill: colEven }
];
const aniKfOdd = [
{ fill: colOdd }
];
const aniKfRev = [
{ fill: colBase }
];<i>
</i>
`</CODE>
As here are the changes I wanna make. But I need to replace the paths,length with the new paths const, for ex <C>
preloaderPaths</C>.

I tried adding paths as a parameter like this <C>
makeAnimation(paths)` but if I do that the function won't work anymore.The original function won't work
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — Just a hint to start with: Create a wrapping funtion where all the variable values are handed over as parameters (not tested):
``<i>
</i> function aniWrapper(
selContainer, // the selector of the container for the SVG or logo
duration, // the duration
colOdd, // color for odd paths
colEven, // color for even paths
colBase, // base color
mode // mode, 'forwards' or 'backwards'
) {

const stepDelay = 50;
// Get DOM element of the container (SVG):
const logo = document.querySelector(selContainer);
// Get paths inside that container:
const paths = logo.querySelectorAll('#svg-logo path');
const aniKfEven = [
{ fill: colEven }
];
const aniKfOdd = [
{ fill: colOdd }
];
const aniKfRev = [
{ fill: colBase }
];
function makeAnimation() {
for (let i = 0; i &lt; paths.length; i++) {
const aniKf = i % 2 ? aniKfOdd : aniKfEven;
const aniOpt = {
delay: i * stepDelay,
duration: aniDuration,
iterations: 1,
fill: 'forwards',
direction: 'normal',
easing: aniEasing
};
ani = paths[paths.length - i - 1].animate(aniKf, aniOpt);
ani.play();
}
}
function makeAnimationRev() {
for (let i = 0; i &lt; paths.length; i++) {
const aniKf = aniKfRev;
const aniOpt = {
delay: i * stepDelay,
duration: aniDuration,
iterations: 1,
fill: 'forwards',
direction: 'normal',
easing: aniEasing
};
ani = paths[i].animate(aniKf, aniOpt);
ani.play();
}
}
// Start animation according mode:
if (mode == 'forwards') {
makeAnimation();
} else {
makeAnimationRev();
}
}<i>
</i>
``
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635217 How am I initializing them? and when I am using it for the second logo am I just copping the wrapper function with the const and jut call the make animation?
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 —  >am I just copping the wrapper function with the const and jut call the make animation?

No, the objective is to use the wrapper function for both, the first and the second logo. Don't duplicate it.
``<i>
</i>&lt;svg id="svg-1"&gt;markup of the first svg here&lt;/svg&gt;
&lt;svg id="svg-2"&gt;markup of the second svg here&lt;/svg&gt;
&lt;script&gt;
function aniWrapper(
// parameters as posted above
) {
// body as posted above
}
// Calling the wrapper function like this will animate the first logo forwards:
aniWrapper('#svg-1', 300, 'blue', 'lightblue', 'white', 'forwards');
&lt;/script&gt;<i>
</i>
``

Not tested, just to demonstrate how to call the function.
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635219 Got it!

I'll try and will come back
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635219 I'm doing something wrong
``<i>
</i>const logoAnimation = (logoContainer, logoPaths) =&gt; {
const logo = document.querySelector(logoContainer);
const paths = document.querySelectorAll(logoPaths);

const stepDelay = 50;
const animationDuration = 150;
const animationEasing = "ease-in-out";
const evenColor = "#736ced";
const oddColor = "#9f9fed";
const baseColor = "white";

const evenKeyframes = [{ fill: evenColor }];
const oddKeyframes = [{ fill: oddColor }];
const reversKeyframes = [{ fill: baseColor }];

const forwardAnimation = () =&gt; {
for (let i = 0; i &lt; paths.length; i++) {
const animationKeyframes = i % 2 ? oddKeyframes : evenKeyframes;
const animationOption = {
delay: i * stepDelay,
duration: animationDuration,
iterations: 1,
fill: "forwards",
direction: "normal",
easing: animationEasing,
};
animation = paths[paths.length - i - 1].animate(
animationKeyframes,
animationOption
);
animation.play();
}
};

const reverseAnimation = () =&gt; {
for (let i = 0; i &lt; paths.length; i++) {
const animationKeyframes = reversKeyframes;
const animationOption = {
delay: i * stepDelay,
duration: animationDuration,
iterations: 1,
fill: "forwards",
direction: "normal",
easing: animationEasing,
};
animation = paths[paths.length - i - 1].animate(
animationKeyframes,
animationOption
);
animation.play();
}
};
logo.addEventListener("mouseover", forwardAnimation);
logo.addEventListener("mouseout", reverseAnimation);
};

logoAnimation("svg-logo-animated", "svg-logo-animated path");<i>
</i>
``
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — Any hints in the console?
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635224 add events listener of null
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — I assume that "svg-logo-animated" is the ID of the logo SVG? If so you have to prepend a "#":

`logoAnimation("#svg-logo-animated", "#svg-logo-animated path");`
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635226 yes, yes it is
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @RaulRogojan#1635227 Yes, it worked. Ill try to implement it on the preloader and i will come back
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635226 it doesn't work when calling it in another function. Also, I notice that I need forwardAnimation and Backwardanimation to run on a setInterval. I think I need to add the parameters to the forardAnimation and backrowardAnimation and recall thoes.

``<i>
</i>const preloaderAnimation = () =&gt; {
logoAnimation("#preloader-logo", "#preloader-logo paths");
};

preloaderAnimation();<i>
</i>
``
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — I recommend coding it as I previously posted: Don't include adding the event listeners and control the direction of the animation by an appropriate parameter (the last one `mode` in my code).

Then you have a core function doing the pure animation. Add the eventlisteners for mouseover/-out outside the function. Use a setInterval for the animation of the preloader.
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — As I found this subject interesting I went ahead and created an object oriented version:
``<i>
</i> &lt;section&gt;
&lt;svg id="svg-logo" width="388" height="233" viewBox="0 0 388 233" fill="none"
xmlns="http://www.w3.org/2000/svg"&gt;
&lt;!-- content as previously used --&gt;
&lt;/svg&gt;
&lt;svg id="svg-loader" width="388" height="233" viewBox="0 0 388 233" fill="none"
xmlns="http://www.w3.org/2000/svg"&gt;
&lt;!-- content as previously used --&gt;
&lt;/svg&gt;
&lt;/section&gt;
&lt;script&gt;
class animateSvg {
// Constructor: This function is called once when the object is created:
constructor(
selContainer, // the selector of the container for the SVG or logo
duration, // the duration
colOdd, // color for odd paths
colEven, // color for even paths
colBase
) {
this.stepDelay = 50;
// Get DOM element of the container (SVG):
this.logo = document.querySelector(selContainer);
// Get paths inside that container:
this.paths = this.logo.querySelectorAll('path');
this.aniDuration = duration;
this.aniEasing = 'ease-in-out';
this.aniKfEven = [
{ fill: colEven }
];
this.aniKfOdd = [
{ fill: colOdd }
];
this.aniKfRev = [
{ fill: colBase }
];
}
makeAnimation() {
for (let i = 0; i &lt; this.paths.length; i++) {
const aniKf = i % 2 ? this.aniKfOdd : this.aniKfEven;
const aniOpt = {
delay: i * this.stepDelay,
duration: this.aniDuration,
iterations: 1,
fill: 'forwards',
direction: 'normal',
easing: this.aniEasing
};
const ani = this.paths[this.paths.length - i - 1].animate(aniKf, aniOpt);
ani.play();
}
}
makeAnimationRev() {
for (let i = 0; i &lt; this.paths.length; i++) {
const aniKf = this.aniKfRev;
const aniOpt = {
delay: i * this.stepDelay,
duration: this.aniDuration,
iterations: 1,
fill: 'forwards',
direction: 'normal',
easing: this.aniEasing
};
const ani = this.paths[i].animate(aniKf, aniOpt);
ani.play();
}
}
makeAnimationBack() {
for (let i = 0; i &lt; this.paths.length; i++) {
const aniKf = this.aniKfRev;
const aniOpt = {
delay: i * this.stepDelay,
duration: this.aniDuration,
iterations: 1,
fill: 'forwards',
direction: 'normal',
easing: this.aniEasing
};
const ani = this.paths[this.paths.length - i - 1].animate(aniKf, aniOpt);
ani.play();
}
}
}

// Create object for animating the logo:
const animateLogo = new animateSvg(
'#svg-logo', // the selector of the container for the logo
300, // the duration
'lightblue', // color for odd paths
'purple', // color for even paths
'white'
);
const logo = document.querySelector('#svg-logo')
logo.addEventListener('mouseover', event =&gt; {
animateLogo.makeAnimation();
});
logo.addEventListener('mouseout', event =&gt; {
animateLogo.makeAnimationRev();
});

// Create object for animating the loader:
const animateLoader = new animateSvg(
'#svg-loader', // the selector of the container for the loader
300, // the duration
'red', // color for odd paths
'lightsalmon', // color for even paths
'white'
);
const aniTime = 300;
function aniPhase1() {
// Animation phase 1
animateLoader.makeAnimation();
// After timeout: Start animation phase 2
setTimeout(aniPhase2, aniTime);
}
function aniPhase2() {
// Animation phase 2
animateLoader.makeAnimationBack();
// After timeout: Start animation phase 1
setTimeout(aniPhase1, aniTime);
}
aniPhase1();
&lt;/script&gt;<i>
</i>
``
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635241 This beats me xd.

I tried doing what you said above, as the oo is a big beyond my understanding yet.

I get that the logo is not defined in the console.
``<i>
</i>const logoAnimation = () =&gt; {
const wrapperFunction = (logoContainer, logoPaths) =&gt; {
const logo = document.querySelector(logoContainer);
const paths = document.querySelectorAll(logoPaths);

const stepDelay = 50;
const animationDuration = 150;
const animationEasing = "ease-in-out";
const evenColor = "#736ced";
const oddColor = "#9f9fed";
const baseColor = "white";

const evenKeyframes = [{ fill: evenColor }];
const oddKeyframes = [{ fill: oddColor }];
const reversKeyframes = [{ fill: baseColor }];

const forwardAnimation = () =&gt; {
for (let i = 0; i &lt; paths.length; i++) {
const animationKeyframes = i % 2 ? oddKeyframes : evenKeyframes;
const animationOption = {
delay: i * stepDelay,
duration: animationDuration,
iterations: 1,
fill: "forwards",
direction: "normal",
easing: animationEasing,
};
animation = paths[paths.length - i - 1].animate(
animationKeyframes,
animationOption
);
animation.play();
}
};

const reverseAnimation = () =&gt; {
for (let i = 0; i &lt; paths.length; i++) {
const animationKeyframes = reversKeyframes;
const animationOption = {
delay: i * stepDelay,
duration: animationDuration,
iterations: 1,
fill: "forwards",
direction: "normal",
easing: animationEasing,
};
animation = paths[paths.length - i - 1].animate(
animationKeyframes,
animationOption
);
animation.play();
}
};
};
wrapperFunction("#preloader-logo", "#preloader-logo paths");

logo.addEventListener("mouseover", forwardAnimation);
logo.addEventListener("mouseout", reverseAnimation);
};

logoAnimation();<i>
</i>
``
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — The reason is that `logo` is defined inside the wrapper function and cannot be accessed from outside. The same applies to the functions forwardAnimation and backwardAnimation.
Copy linkTweet thisAlerts:
@codyhillauthorAug 07.2021 — @Sempervivum#1635251 Yeah, I figure that out. But how can I not use the mouse event in the function that I need to recall on the preloader and set an interval function using the forward and backward function? I tried in the preloader function to call only the forward and backward function using the same variables name and in the animate logo function and that did not work either. Should I add the preloader function inside the animate function and use parameters for SVG paths?
Copy linkTweet thisAlerts:
@SempervivumAug 07.2021 — OOP is the key for fixing all these issues. I modified your latest code to some old school style. Read my comments Mod1, Mod2, ...:
``<i>
</i> // const logoAnimation = () =&gt; { Mod1: Remove wrapper

// const wrapperFunction = (logoContainer, logoPaths) =&gt; {
wrapperFunction = function (logoContainer, logoPaths) { // Mod2: Change syntax so the function can be used as a constructor
const logo = document.querySelector(logoContainer);
const paths = document.querySelectorAll(logoPaths);

const stepDelay = 50;
const animationDuration = 150;
const animationEasing = "ease-in-out";
const evenColor = "#736ced";
const oddColor = "#9f9fed";
const baseColor = "white";

const evenKeyframes = [{ fill: evenColor }];
const oddKeyframes = [{ fill: oddColor }];
const reversKeyframes = [{ fill: baseColor }];

// const forwardAnimation = () =&gt; {
this.forwardAnimation = () =&gt; { // Mod3: Make this function accessible from outside
for (let i = 0; i &lt; paths.length; i++) {
const animationKeyframes = i % 2 ? oddKeyframes : evenKeyframes;
const animationOption = {
delay: i * stepDelay,
duration: animationDuration,
iterations: 1,
fill: "forwards",
direction: "normal",
easing: animationEasing,
};
animation = paths[paths.length - i - 1].animate(
animationKeyframes,
animationOption
);
animation.play();
}
};

// const reverseAnimation = () =&gt; {
this.reverseAnimation = () =&gt; { // Mod4: Make this function accessible from outside
for (let i = 0; i &lt; paths.length; i++) {
const animationKeyframes = reversKeyframes;
const animationOption = {
delay: i * stepDelay,
duration: animationDuration,
iterations: 1,
fill: "forwards",
direction: "normal",
easing: animationEasing,
};
animation = paths[paths.length - i - 1].animate(
animationKeyframes,
animationOption
);
animation.play();
}
};
};

// Mod5: Create an object instead of simply calling the function
// Subsequently you will be able to call the functions from outside:
const animatePreloader = new wrapperFunction("#preloader-logo", "#preloader-logo path");

const preloader = document.querySelector('#preloader-logo');

// Mod6: Call forwardAnimation from object:
preloader.addEventListener("mouseover", animatePreloader.forwardAnimation);

// Mod7: Call reverseAnimation from object:
preloader.addEventListener("mouseout", animatePreloader.reverseAnimation);

// }; Mod1: Remove wrapper<i>
</i>
``
×

Success!

Help @codyhill spread the word by sharing this article on Twitter...

Tweet This
Sign in
Forgot password?
Sign in with TwitchSign in with GithubCreate Account
about: ({
version: 0.1.9 BETA 5.13,
whats_new: community page,
up_next: more Davinci•003 tasks,
coming_soon: events calendar,
social: @webDeveloperHQ
});

legal: ({
terms: of use,
privacy: policy
});
changelog: (
version: 0.1.9,
notes: added community page

version: 0.1.8,
notes: added Davinci•003

version: 0.1.7,
notes: upvote answers to bounties

version: 0.1.6,
notes: article editor refresh
)...
recent_tips: (
tipper: @AriseFacilitySolutions09,
tipped: article
amount: 1000 SATS,

tipper: @Yussuf4331,
tipped: article
amount: 1000 SATS,

tipper: @darkwebsites540,
tipped: article
amount: 10 SATS,
)...