nazarpunk
nazarpunk

Reputation: 226

Apply the perspective property to the matrix given by transform:matrix3d()

It is necessary to rotate the block through transform:matrix3d() using js. Setting perspective via css is not an option, because it will break the rest. I have achieved the desired rotation (block A), but trying to move the block back (block B) something goes wrong.

How to correctly apply css perspective directly to the matrix?

const a = document.querySelector('.a .block');
const b = document.querySelector('.b .block');

window.addEventListener('mousemove', e => {
    const d = clamp((e.pageY - innerHeight * .5) / 400, -1, 1);
    const v = lerp(0, Math.PI, d);

    const x = 160 * .5;
    const y = 106 * .5;

    const m = [1, 0, 0, 0, /**/ 0, 1, 0, 0, /**/ 0, 0, 1, 0, /**/ 0, 0, 0, 1];
    const p = [1, 0, 0, 0, /**/ 0, 1, 0, 0, /**/ 0, 0, 1, -1 / 800, /**/ 0, 0, 0, 1];

    multiply(m, p);
    translate(m, -x, y, 0);
    rotateX(m, v);

    translate(m, 0, -y, 0);
    a.style.transform = `matrix3d(${m.join(',')})`;

    translate(m, x);
    b.style.transform = `matrix3d(${m.join(',')})`;
});

const translate = (a, x = 0, y = 0, z = 0) => {
    a[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
    a[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
    a[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
    a[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
};

const rotateX = (m, rad) => {
    const s = Math.sin(rad),
        c = Math.cos(rad),
        a4 = m[4],
        a5 = m[5],
        a6 = m[6],
        a7 = m[7],
        a8 = m[8],
        a9 = m[9],
        a10 = m[10],
        a11 = m[11];
    m[4] = a4 * c + a8 * s;
    m[5] = a5 * c + a9 * s;
    m[6] = a6 * c + a10 * s;
    m[7] = a7 * c + a11 * s;
    m[8] = a8 * c - a4 * s;
    m[9] = a9 * c - a5 * s;
    m[10] = a10 * c - a6 * s;
    m[11] = a11 * c - a7 * s;
};

const multiply = (a, b) => {
    const a0 = a[0],
        a1 = a[1],
        a2 = a[2],
        a3 = a[3],
        a4 = a[4],
        a5 = a[5],
        a6 = a[6],
        a7 = a[7],
        a8 = a[8],
        a9 = a[9],
        a10 = a[10],
        a11 = a[11],
        a12 = a[12],
        a13 = a[13],
        a14 = a[14],
        a15 = a[15];

    let b0 = b[0],
        b1 = b[1],
        b2 = b[2],
        b3 = b[3];

    a[0] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[1] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[2] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[3] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;

    b0 = b[4];
    b1 = b[5];
    b2 = b[6];
    b3 = b[7];
    a[4] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[5] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[6] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[7] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;

    b0 = b[8];
    b1 = b[9];
    b2 = b[10];
    b3 = b[11];
    a[8] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[9] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[10] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[11] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;

    b0 = b[12];
    b1 = b[13];
    b2 = b[14];
    b3 = b[15];
    a[12] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[13] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[14] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[15] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;
};

const clamp = (num, min, max) => Math.min(Math.max(num, min), max);

const lerp = (a, b, t) => a * (1 - t) + b * t;
body {
    padding: 0;
}

.wrap {
    position: absolute;
    left: 90px;
    width: 160px;
    height: 106px;
    background-color: gray;
}

.wrap.a {
    top: 20px;
}

.wrap.b {
    top: 140px;
}

.block {
    font-size: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    transform-origin: 0 0;
    color: white;
    background: radial-gradient(circle, rgba(63, 94, 251, 1) 0%, rgba(252, 70, 107, 1) 100%);
}
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>matrix 3d</title>
    <link rel="stylesheet" href="main.css">
    <script src="main.mjs" type="module" defer></script>
</head>
<body>
<div class="wrap a">
    <div class="block">A</div>
</div>
<div class="wrap b">
    <div class="block">B</div>
</div>
</body>
</html>

Upvotes: 0

Views: 51

Answers (1)

Jerome Demantke
Jerome Demantke

Reputation: 335

To prevent the left side of B from being vertical, you can use the "transform-origin" property

const a = document.querySelector('.a .block');
const b = document.querySelector('.b .block');

window.addEventListener('mousemove', e => {
    const d = clamp((e.pageY - innerHeight * .5) / 400, -1, 1);
    const v = lerp(0, Math.PI, d);

    const x = 160 * .5;
    const y = 106 * .5;

    const m = [1, 0, 0, 0, /**/ 0, 1, 0, 0, /**/ 0, 0, 1, 0, /**/ 0, 0, 0, 1];
    const p = [1, 0, 0, 0, /**/ 0, 1, 0, 0, /**/ 0, 0, 1, -1 / 800, /**/ 0, 0, 0, 1];

    multiply(m, p);
    translate(m, -x, y, 0);
    rotateX(m, v);

    translate(m, 0, -y, 0);
    a.style.transform = `matrix3d(${m.join(',')})`;

    translate(m, x);
    b.style.transform = `matrix3d(${m.join(',')})`;
});

const translate = (a, x = 0, y = 0, z = 0) => {
    a[12] = a[0] * x + a[4] * y + a[8] * z + a[12];
    a[13] = a[1] * x + a[5] * y + a[9] * z + a[13];
    a[14] = a[2] * x + a[6] * y + a[10] * z + a[14];
    a[15] = a[3] * x + a[7] * y + a[11] * z + a[15];
};

const rotateX = (m, rad) => {
    const s = Math.sin(rad),
        c = Math.cos(rad),
        a4 = m[4],
        a5 = m[5],
        a6 = m[6],
        a7 = m[7],
        a8 = m[8],
        a9 = m[9],
        a10 = m[10],
        a11 = m[11];
    m[4] = a4 * c + a8 * s;
    m[5] = a5 * c + a9 * s;
    m[6] = a6 * c + a10 * s;
    m[7] = a7 * c + a11 * s;
    m[8] = a8 * c - a4 * s;
    m[9] = a9 * c - a5 * s;
    m[10] = a10 * c - a6 * s;
    m[11] = a11 * c - a7 * s;
};

const multiply = (a, b) => {
    const a0 = a[0],
        a1 = a[1],
        a2 = a[2],
        a3 = a[3],
        a4 = a[4],
        a5 = a[5],
        a6 = a[6],
        a7 = a[7],
        a8 = a[8],
        a9 = a[9],
        a10 = a[10],
        a11 = a[11],
        a12 = a[12],
        a13 = a[13],
        a14 = a[14],
        a15 = a[15];

    let b0 = b[0],
        b1 = b[1],
        b2 = b[2],
        b3 = b[3];

    a[0] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[1] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[2] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[3] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;

    b0 = b[4];
    b1 = b[5];
    b2 = b[6];
    b3 = b[7];
    a[4] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[5] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[6] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[7] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;

    b0 = b[8];
    b1 = b[9];
    b2 = b[10];
    b3 = b[11];
    a[8] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[9] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[10] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[11] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;

    b0 = b[12];
    b1 = b[13];
    b2 = b[14];
    b3 = b[15];
    a[12] = b0 * a0 + b1 * a4 + b2 * a8 + b3 * a12;
    a[13] = b0 * a1 + b1 * a5 + b2 * a9 + b3 * a13;
    a[14] = b0 * a2 + b1 * a6 + b2 * a10 + b3 * a14;
    a[15] = b0 * a3 + b1 * a7 + b2 * a11 + b3 * a15;
};

const clamp = (num, min, max) => Math.min(Math.max(num, min), max);

const lerp = (a, b, t) => a * (1 - t) + b * t;
body {
    padding: 0;
}

.wrap {
    position: absolute;
    left: 90px;
    width: 160px;
    height: 106px;
    background-color: gray;
}

.wrap.a {
    top: 20px;
}

.wrap.b {
    top: 140px;
}

.block.a {
  transform-origin: 0 0;
}

.block.b {
  transform-origin: center 0;
}

.block {
    font-size: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    
    color: white;
    background: radial-gradient(circle, rgba(63, 94, 251, 1) 0%, rgba(252, 70, 107, 1) 100%);
}
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>matrix 3d</title>
    <link rel="stylesheet" href="main.css">
    <script src="main.mjs" type="module" defer></script>
</head>
<body>
<div class="wrap a">
    <div class="block a">A</div>
</div>
<div class="wrap b">
    <div class="block b">B</div>
</div>
</body>
</html>

Upvotes: 1

Related Questions