Why is my order of transformation applied affecting the way my code behaves? [duplicate]

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.025;
const damping = 0.845;

const maxSqueeze = 0.5;
const maxStretch = 1.5;
const maxMouseSpeed = 5;

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
});
body {
    background-color: #1d1f22;
}

.circle {
    --circle-size: 30px;

    position: fixed;
    top: calc(var(--circle-size) / 2 * -1);
    left: calc(var(--circle-size) / 2 * -1);
    pointer-events: none;
    box-sizing: border-box;
    width: var(--circle-size);
    height: var(--circle-size);
    /* border: 2px solid #c1bb98; */
    background-color: #c1bb98;
    mix-blend-mode: difference;
    border-radius: 100%;
    transform: rotate(45deg);

    transition: opacity 0.15s ease-out;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Document</title>
</head>
<body>
    
    <div class="circle"></div>
    <script src="script.js"></script>

</body>
</html>

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.

5

It’s probably because you’re missing a closing parenthesis in rotateElement.

3

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật