上下文:
在我的Web应用程序中,我创建了几组元素,这些元素在3D空间中相对于彼此相对。所有元素均具有变换风格:Preserve-3D
。父元素用于响应用户导航来旋转,扩展和移动所有这些元素。
其中一些元素是“门户”,即矩形窗户,可以看到其他几个元素。重要的是要注意,这些“门户”中的元素必须与母体门户外的元素相同的全局3D空间(不仅是2D)中存在。
这些“门户”具有溢出:隐藏
,以隐藏其中的溢出元素。
根据W3 Spec , Overflow
是几个CSS属性值之一,它们将对象将对象组合在一起并创建2D“平坦”渲染上下文;实际上与“转换风格:平坦”相同的结果;
问题:
我必须找到某种方法来倒数(取消)由 transform-transform-style创建的转换矩阵:Flat;
并创建一个相同的 3D渲染上下文使用 transform-transform-style:preserve-style:preserve- 3D;
所有3D CSS变换均在内部由3D矩阵表示。在转换风格的情况下:Flat
用户代理在其自己的转换矩阵上进行了一些神秘的“平坦”数学。然后,这个矩阵被应用于孩子,产生了一种幻想,即它的孩子都在父母的窗格中被扁平化。应该可以通过少量矩阵数学绕过此效果。
不幸的是,W3规格没有规定这种“扁平化”在数学上的含义。他们对这个主题很含糊,只需称其为“扁平的(元素)内容的表示” %7E:text =有%20NO%20DEPTH“ rel =“ nofollow noreferrer”>“没有深度。”
我不知道需要做些什么矩阵数学来复制“扁平”效果。如果可以对算法进行逆向设计,则可以通过倒置矩阵轻松否定这种“平坦”行为。每个门户网站元素内部的次要元素可用于将此反转矩阵应用于转换,从而否定了“扁平”变换并正确恢复3D位置/旋转/透视图。
下面显示的是一个演示,表明它绝对有可能在扁平的父母内部产生深度的印象。在这种情况下,我天真地应用了最高的父母转换的倒数。不幸的是,颠倒“扁平”矩阵并不是那么简单:
demo and links:
注释:注释:
- 涉及使用面具,剪辑path的问题的解决方案,其他可能无法正常工作,因为它们也分组属性值
- 除了上述外,剪辑paths在
- 算法应适用于不同的父位置,旋转,尺度和透视图。我不是在寻找可以解决我的示例和示例的魔术变换价值。
- 我已经使用几个独立的3D上下文来解决此问题,彼此之间分层。剪辑路径用于切割门户。但是,这导致了问题,当需要在其他层中其他门户或元素掩盖门户网站时。
- 考虑到我的项目背景,我对其他替代建议开放。
- 之前提出过一个类似的问题,但是在OP正在寻找仅CSS的解决方案的情况下: css:“溢出:隐藏的”替代方案不会破坏3D变换
编辑1:为什么不使用三个JS?
我的整个应用程序/网站将建立在3D空间中存在的导航模型上,那么为什么不使用WebGL呢?
- 搜索引擎不能解析WebGL,或者屏幕读取器
- WebGL不能静态生成,并且水合客户端
- 我的应用程序将包括可相互作用的HTML。这不能在WebGL中完成:
从技术上讲,有多种方法可以进行混合HTML/WebGL网站,但它需要CSS转换,并且(对于我的用例)动态计算了剪辑,这在 chrome 和溢出:隐藏,这使我提出了这个问题。
- 在WebGL中构建此功能将需要大量额外的工作设计和构建导航和生命周期系统,否则我可以从现有的HTML框架中利用它们……我是一个人团队。
- 最后,WebGL中的门户网站并不是一个简单的问题,我能够通过“通过”门户飞行的要求使该目标更加崇高...如果我的目标是仅仅是我的目标,我需要以最简单的方式来创建一个MVP并将其拿到我耐心等待的测试人员。
就我的测试/研究而言,WebGL并不是我要建造的东西的可行替代方案(至少对于一个人团队而言),但是,弄清楚一些数学应该是可行的。
编辑2:部分解决方案
得益于@markus的回答,我发现可以通过删除与z轴转换相关的列中的所有值来实现扁平效果。这可以通过将其转换乘以以下矩阵来完成:
采用身份矩阵,然后将第三列中的第三项更改为任意的小数。
const flatten = identity()
flatten[10] = 1/10000 // arbitrarily small. not zero, since that will invalidate the matrix.
这是一个演示:
这似乎表明,在内部,浏览器将3d 4x4矩阵转换为2d 3x3矩阵,通过将值放在其第三列中。
因此,可以合理地认为颠倒这种效果就像重新填充第三列一样简单:
// normalize portal transform
const normalizedPortalTransform = normalize(portalTransform)
// try to re-establish perspective in z axis
const final = identity()
final[8] = normalizedPortalTransform[8]
final[9] = normalizedPortalTransform[9]
final[10] = normalizedPortalTransform[10]
final[11] = normalizedPortalTransform[11]
它似乎是有点工作,但是观点仍然关闭:
这是一个演示: https://jsfiddle.net/aywbe9p7/3/3/
演示重新填充矩阵的许多不同组合,例如包括第三行(索引2、6、10和14),甚至使用下面的代码分解 portaltransform
的观点组件,并尝试尝试尝试将这些值重新组合到最终
de-flatting矩阵中。但这也行不通。
// Returns the transpose of a 4x4 matrix
function transpose(matrix){
return Array.from({ length: 16 }, (_, i) => {
const y = i % 4
const x = Math.floor(i / 4)
return matrix[y * 4 + x]
})
}
// Decompose the perspective component of a 4x4 matrix.
// https://www.w3.org/TR/css-transforms-2/#decomposing-a-3d-matrix
function decompPerspective(matrix){
// There exists some perspective
if(matrix[15] != 0 && (matrix[3] != 0 || matrix[7] != 0 || matrix[11] != 0)){
// Normalize the matrix.
const m = normalize(matrix)
// Used to solve for perspective
const perspectiveMatrix = Array.from(m)
perspectiveMatrix[3] = 0
perspectiveMatrix[7] = 0
perspectiveMatrix[11] = 0
perspectiveMatrix[15] = 1
// The right hand side of the equation.
const r0 = m[3]
const r1 = m[7]
const r2 = m[11]
const r3 = m[15] // should be 1
// Solve the equation by inverting perspectiveMatrix and multiplying
// rightHandSide by the inverse.
const f = transpose(inverse(perspectiveMatrix))
return [
f[0] * r0 + f[4] * r1 + f[8] * r2 + f[12] * r3,
f[1] * r0 + f[5] * r1 + f[9] * r2 + f[13] * r3,
f[2] * r0 + f[6] * r1 + f[10] * r2 + f[14] * r3,
f[3] * r0 + f[7] * r1 + f[11] * r2 + f[15] * r3, // should be 1
]
}
// No perspective
else{
return [0, 0, 0, 1]
}
}
编辑2:为什么数学需要完美而不是近似
这对于我而不制作动画可能会很难解释,但是想象您正在看不起包含门户的场景。该门户内部是另一个场景。
我想在观看顶级场景和嵌入门户中的场景之间进行动画动画。
为了做到这一点,视觉视角首先是动画以“飞向门户”元素的动画。
接下来,当视口完全由门户填充时,包含门户的场景将被删除。它被嵌入式场景取代,只是现在门户网站已经消失了,因此场景不再在门户内部。
为了使这种过渡起作用,门户网站中项目的视角必须与门户外部外部相同项目的视角完美匹配……否则,这种过渡将不会平稳。
让我知道这是否没有意义。我也许可以做一个插图来更好地证明这一点。
Context:
In my web app I create several groups of elements all positioned relative to each other in 3d space. All elements have transform-style: preserve-3d
. A parent element is used to rotate, scale, and move all these elements together in response to user navigation.
Some of these elements are "portals," rectangular windows through which several other elements might be visible. It is important to note that the elements within these "portals" must exist within the same global 3d space (not just 2d) as elements outside their parent portal.
These "portals" have overflow: hidden
in order to hide overflowing elements within them.

As per the w3 spec, overflow
is one of several css property values that group objects together and creates a 2d “flattened” rendering context; effectively the same result as “transform-style: flat;”
Question:
I must find some way to inverse (cancel-out) the transformation matrix created by transform-style: flat;
and create an identical 3D rendering context to one preserved using transform-style: preserve-3d;
All 3D css transforms are represented by a 3d matrix internally. In the case of transform-style: flat
the user-agent is doing some mysterious "flattening" math on its own transformation matrix. Then this matrix gets applied to its children, creating the illusion that its children are all flattened in their parent's pane. It should be possible to bypass this effect with a little matrix math.
Unfortunately, the w3 spec makes no specification of what this "flattening" means mathematically; they are rather vague on the subject, simply calling it a "flattened representation of their (the element's) content" that "has no depth."
I can't figure out what matrix math needs to be done to reproduce the "flattening" effect. If the algorithm could be reverse engineered, this "flattening" behavior could be easily negated with an inverted matrix. A secondary element inside each portal element could be used to apply this inverted matrix as a transform, thereby negating the "flattened" transform and correctly restoring 3d position/rotation/perspective.
Shown below is a demo demonstrating that is is absolutely possible to create the impression of depth within a flattened parent. In this case, I naively apply the inverse of the top-most parent's transform. Unfortunately, inverting a "flattened" matrix is not so simple:

Demo and Links:
Notes:
- Solutions involving bypassing the problem using masks, clip-paths, other likely won't work since they are also grouping property values
- In addition to the above, clip-paths have rendering issues on chrome and firefox
- Algorithm should work for different parent positions, rotations, scales and perspective. I'm not looking for a magic transform value that will fix my example and my example only.
- I've experimented solving this issue using several independent 3d contexts layered on top of each other. Clip paths were used to cut out portals. However, this resulted in issues when portals need to be obscured by other portals or elements within other layers.
- I am open to other alternative suggestions given the context of my project.
- A similar question has been asked before, however in a much different context where the OP is looking for a css-only solution: CSS: "overflow: hidden" alternative that doesn't break 3D transforms
Edit 1: Why not use three js?
My entire application/website will be built on a model of navigation that exists in 3d space, so why not just use webgl?
- Webgl cannot be parsed by search engines or screen readers
- Webgl cannot be statically generated and hydrated client-side
- My application will include interactable html. This can't be done in webgl:
Technically, there are ways to do hybrid html/webgl websites but it requires css transforms and (for my use case) dynamically calculated clipping, which does not work on chrome and firefox. The only only other way to do clipping is to use overflow: hidden
, which lead me to asking this question.
- Building this in webgl would require a ton of extra work designing and building navigation and lifecycle systems that I can otherwise leverage from existing html frameworks... I'm a one person team.
- Finally, portals in webgl are not a simple issue to solve, and my requirement of being able to fly "through" portals makes that goal even more lofty... I need to get this working the simplest way possible if my goal is just to create a mvp and get it out to my patiently waiting testers.
As far as my testing/research goes webgl isn't a feasible alternative for what I'm building, (at least for a one person team) however, figuring out a bit of math should be doable.
Edit 2: A partial solution
Thanks to @Markus' answer, I've found that the flattening effect can be achieved by dropping all the values in the column associated with transformations in the z-axis. This can be done by multiplying it's transform by the following matrix:
Take the identity matrix, and change the 3rd item in the 3rd column to a arbitrary small number.
const flatten = identity()
flatten[10] = 1/10000 // arbitrarily small. not zero, since that will invalidate the matrix.
Here's a demo of this: https://jsfiddle.net/aywbe9p7/2/

This seems to suggest that internally, the browser is converting the 3d 4x4 matrix into a 2d 3x3 matrix by dropping the values in it's third column.
So, It would be reasonable to think that inverting this effect would be as simple as re-populating the third column:
// normalize portal transform
const normalizedPortalTransform = normalize(portalTransform)
// try to re-establish perspective in z axis
const final = identity()
final[8] = normalizedPortalTransform[8]
final[9] = normalizedPortalTransform[9]
final[10] = normalizedPortalTransform[10]
final[11] = normalizedPortalTransform[11]
It appears to kind of work, but the perspective is still off:
Here's a demo of this: https://jsfiddle.net/aywbe9p7/3/

I've tried many different combinations of re-populating the matrix, like also including the third row (indexes 2, 6, 10 and 14) and even decomposing the portalTransform
's perspective components using the code below, and trying to re-incorporate these values into the final
de-flattening matrix. But that doesn't work either.
// Returns the transpose of a 4x4 matrix
function transpose(matrix){
return Array.from({ length: 16 }, (_, i) => {
const y = i % 4
const x = Math.floor(i / 4)
return matrix[y * 4 + x]
})
}
// Decompose the perspective component of a 4x4 matrix.
// https://www.w3.org/TR/css-transforms-2/#decomposing-a-3d-matrix
function decompPerspective(matrix){
// There exists some perspective
if(matrix[15] != 0 && (matrix[3] != 0 || matrix[7] != 0 || matrix[11] != 0)){
// Normalize the matrix.
const m = normalize(matrix)
// Used to solve for perspective
const perspectiveMatrix = Array.from(m)
perspectiveMatrix[3] = 0
perspectiveMatrix[7] = 0
perspectiveMatrix[11] = 0
perspectiveMatrix[15] = 1
// The right hand side of the equation.
const r0 = m[3]
const r1 = m[7]
const r2 = m[11]
const r3 = m[15] // should be 1
// Solve the equation by inverting perspectiveMatrix and multiplying
// rightHandSide by the inverse.
const f = transpose(inverse(perspectiveMatrix))
return [
f[0] * r0 + f[4] * r1 + f[8] * r2 + f[12] * r3,
f[1] * r0 + f[5] * r1 + f[9] * r2 + f[13] * r3,
f[2] * r0 + f[6] * r1 + f[10] * r2 + f[14] * r3,
f[3] * r0 + f[7] * r1 + f[11] * r2 + f[15] * r3, // should be 1
]
}
// No perspective
else{
return [0, 0, 0, 1]
}
}
Edit 2: Why the math needs to be perfect and not approximative
This might be a little difficult for me to explain without making an animation, but imagine you are looking down on a scene containing a portal. Inside this portal is another scene.
I would like to animate between looking at the top-level scene, and looking into the scene embedded within the portal.
In order to do this, the visual perspective is first animated to "fly" towards the portal element.
Next, when the viewport is filled completely by the portal, the scene containing the portal is deleted. It is replaced with the embedded scene, except that now the portal is gone, so the scene is not inside a portal anymore.
For this transition to work, the perspective of items in a portal must match the perspective of those same items outside a portal perfectly... otherwise this transition will not be smooth.
Let me know if that doesn't make sense. I might be able to make an illustration to demonstrate this better.
发布评论
评论(1)
如果您不想使用某些WebGl-lib,例如
three.js
,但纯CSS-Transform,获得所需效果的直接方法是动态更改请注意,Firefox仍然与3D-CSS存在一些问题。例如,我建议不要在您的对象上使用任何边距或衬垫。
正常的扁平化由相机矩阵(请参阅 mdn> mdn )。它是透视值的函数。如果用户的视图不是投影屏幕(在您的情况下是门户平面)的正交,则可以相应地更改
perspective-Origin
。像您的示例中,我使用
rematrix
来调整门户网站的透视图。这是一个图表,用于勾勒出调整后的透视原孔的计算背后的想法:
If you don't want to use some WebGL-lib like
three.js
but pure css-transform, the straight forward way to get the desired effect is to dynamically change the perspective-origin of your portal.Please be aware that Firefox still has some issues with 3D-css. E.g. I recommend not to use any margins or paddings on your objects.
The normal flattening is done by the camera matrix that can be found in the documentation of the perspective parameter (see MDN). It is a function of the perspective value. If the user's view is not orthogonal to the projection screen (which in your case is the portal plane) you can change the
perspective-origin
accordingly.Like in your examples I have used
Rematrix
to adjust the perspective-origin of the portal window.Here is a diagram to sketch the idea behind the calculation of the adjusted perspective-origin:
