You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在transform:matrix3d()中直接应用CSS透视?移动元素异常排查

如何将CSS透视正确直接应用到matrix3d矩阵中?

问题描述

需要通过JavaScript使用transform:matrix3d()实现块元素的旋转,由于会破坏其他效果,无法通过CSS设置perspective。目前已成功实现块A的目标旋转,但在移动块B时出现异常。

问题分析

问题根源在于两点:

  1. 透视矩阵的应用顺序错误:透视作为视图变换,应该在元素的所有局部变换(平移、旋转)完成后再应用,否则会导致透视效果与元素位置不匹配。
  2. 变换矩阵复用导致叠加错误:复用同一个矩阵处理A和B,会让B的变换叠加A的后续平移操作,最终出现错位。

解决方案

为每个元素创建独立的变换流程,确保:

  • 先完成元素的局部变换(平移到旋转原点→旋转→平移回原位置)
  • 最后将透视矩阵与元素的变换矩阵相乘,得到带透视的最终变换

修正后的代码

JavaScript

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;
    // 透视矩阵(-1/透视距离,这里800是模拟的perspective值)
    const perspectiveMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, -1/800, 0, 0, 0, 1];

    // 处理块A的变换
    const matrixA = getIdentityMatrix();
    translate(matrixA, -x, y, 0);
    rotateX(matrixA, v);
    translate(matrixA, x, -y, 0);
    // 最后应用透视
    multiply(matrixA, perspectiveMatrix);
    a.style.transform = `matrix3d(${matrixA.join(',')})`;

    // 处理块B的变换(独立矩阵)
    const matrixB = getIdentityMatrix();
    translate(matrixB, -x, y, 0);
    rotateX(matrixB, v);
    translate(matrixB, x, -y, 0);
    multiply(matrixB, perspectiveMatrix);
    b.style.transform = `matrix3d(${matrixB.join(',')})`;
});

// 新增获取单位矩阵的函数
const getIdentityMatrix = () => [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];

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;

CSS

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%);
}

HTML

<!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>

关键调整说明

  • 新增getIdentityMatrix()函数,为每个元素创建独立的单位矩阵,避免变换叠加
  • 调整变换顺序:先完成元素的局部旋转变换,最后再与透视矩阵相乘,确保透视正确作用于元素的最终位置
  • 为A和B分别计算变换矩阵,保证两者的变换独立无干扰

内容的提问来源于stack exchange,提问作者nazarpunk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 01:36:21