如何在transform:matrix3d()中直接应用CSS透视?移动元素异常排查
如何将CSS透视正确直接应用到matrix3d矩阵中?
问题描述
需要通过JavaScript使用transform:matrix3d()实现块元素的旋转,由于会破坏其他效果,无法通过CSS设置perspective。目前已成功实现块A的目标旋转,但在移动块B时出现异常。
问题分析
问题根源在于两点:
- 透视矩阵的应用顺序错误:透视作为视图变换,应该在元素的所有局部变换(平移、旋转)完成后再应用,否则会导致透视效果与元素位置不匹配。
- 变换矩阵复用导致叠加错误:复用同一个矩阵处理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
相关产品推荐
相关产品推荐

