I created this simple cursor for fun, it’s a circle div that follows the mouse with some bounciness (spring and damping). I also wanted it to stretch and squeeze based on the velocity of the mouse so I added that as well, and both works. Now I also added rotation based on the velocity direction of the mouse, now that works too (individually). however when I put them all together it doesn’t work as expected, for example:
if I put the final transform like so : circle.style.transform =
${translateElement} ${scaleElement} ${rotateElement};
everything technically works, but the stretch and squeeze is applied on the same axis after the rotation which means it’s always squeezing from top to bottom (visually) and stretching from right to left (again visually). So it doesn’t look like it’s being stretched towards the mouse, which was the desired effect I was going for
now if I put the transform like this : circle.style.transform =
${translateElement} ${rotateElement} ${scaleElement};
the circle div does not move or scale at all (basically the code is not working without console error), Can anyone help me figure out why?
const circle = document.querySelector('.circle');
const mousePos = { x: 0, y: 0 };
const prevMousePos = { x: 0, y: 0 };
let mouseSpeed = 0;
const circlePos = { x: 0, y: 0 };
const circleVelocity = { x: 0, y: 0 };
const spring = 0.020;
const damping = 0.845;
const maxSqueeze = 0.5;
const maxStretch = 1.5;
const maxMouseSpeed = 1.1;
let lowSpeedStartTime = 0;
let lowSpeedDuration = 0;
const lowSpeedThreshold = 0.5; // 0.5 seconds
document.addEventListener('mousemove', (e) => {
const currentMousePos = { x: e.clientX, y: e.clientY };
const dx = currentMousePos.x - prevMousePos.x;
const dy = currentMousePos.y - prevMousePos.y;
// Calculate the speed as the magnitude of the velocity vector
mouseSpeed = Math.sqrt(dx * dx + dy * dy);
// Update previous mouse position
prevMousePos.x = currentMousePos.x;
prevMousePos.y = currentMousePos.y;
// Update the current mouse position
mousePos.x = currentMousePos.x;
mousePos.y = currentMousePos.y;
});
function animate() {
const ax = (mousePos.x - circlePos.x) * spring;
const ay = (mousePos.y - circlePos.y) * spring;
circleVelocity.x += ax;
circleVelocity.y += ay;
circleVelocity.x *= damping;
circleVelocity.y *= damping;
circlePos.x += circleVelocity.x;
circlePos.y += circleVelocity.y;
// calculate rotation angle
const angle = Math.atan2(circleVelocity.y, circleVelocity.x) * 180 / Math.PI;
// Calculate the normalized mouse speed
const normalizedSpeed = Math.min(mouseSpeed, maxMouseSpeed) / maxMouseSpeed;
// Calculate stretch and squeeze factors
const scaleFactor = 1 + (maxStretch - 1) * normalizedSpeed;
const squeezeFactor = 1 - (1 - maxSqueeze) * normalizedSpeed;
if (mouseSpeed <= 1) {
if (lowSpeedStartTime === 0) {
lowSpeedStartTime = performance.now();
}
lowSpeedDuration = (performance.now() - lowSpeedStartTime) / 1000; // in seconds
} else {
lowSpeedStartTime = 0;
lowSpeedDuration = 0;
}
// Apply squeeze/stretch effect
let scaleX = scaleFactor; // Stretch
let scaleY = squeezeFactor; // Squeeze
if (lowSpeedDuration > lowSpeedThreshold) {
scaleX = 1.0; // Squeeze
scaleY = 1.0; // Stretch
}
const translateElement = `translate(${circlePos.x}px, ${circlePos.y}px)`;
const rotateElement = `rotate(${angle}deg`;
const scaleElement = `scale(${scaleX}, ${scaleY})`;
circle.style.transform = `${translateElement} ${rotateElement} ${scaleElement}`;
requestAnimationFrame(animate);
}
animate();
document.addEventListener('mouseleave', () => {
circle.style.opacity = '0'; // Make cursor disappear
});
document.addEventListener('mouseenter', () => {
circle.style.opacity = '1'; // Make cursor reappear when mouse enters the document again
});
I tried changing the shape of the div to check if the rotation is actually being applied on the first order of transforms and it does work, so there is something wrong with the 2nd order of transform which is not working.
Note: the 2nd order of transform is valid in CSS, I have used it before and it works, it just does not work on this particular scenario and I don’t understand why.