css3从方形图像到不带x旋转的梯形

Bac*_*uri 1 css rotation distortion skew css-transforms

我正在尝试将带有背景图像的方形 div 转换为梯形。

\n

我想以 2D 形式制作它,就像 Photoshop 的“扭曲”工具一样。

\n

基本上,我想要的只是缩小正方形的顶边并使图像相应变形。

\n

3D 变换“似乎”达到了目的:

\n
transform: rotateX(30deg);\n
Run Code Online (Sandbox Code Playgroud)\n

它适用于大多数用例,但不是所有用例。\n事实上,它是正方形的 30 度旋转,从正面/背面看时“看起来”像梯形,但当从正面/背面看时,它仍然是 30\xc2\xb0 旋转的正方形从任何其他方面看。

\n

我想要的是得到一个真正的梯形。我希望方形图像以 2D 方式扭曲,以便形状和图像实际上发生变化,而不涉及旋转。

\n

我尝试了这个,它在形状(梯形)方面有效:

\n
border-style: solid;\nheight: 0;\nborder-color: transparent transparent red transparent;\nborder-width: 0 100px 100px 100px;\n
Run Code Online (Sandbox Code Playgroud)\n

但是我无法用失真后的背景图像替换红色区域。这违背了我的目的。\n我尝试的任何尝试都使图片保持不变形。

\n

有没有任何 css/html5/javascript 技巧可以实现我想要的?

\n

谢谢。

\n

Ana*_*Ana 6

您可以通过对伪元素(您还可以在其上设置background-image)应用 3D 变换并确保它在其原始平面(其父元素的平面)上展平来获得效果。这意味着如果您想以 3D 方式旋转某些物体,则必须旋转父物体。

\n

步骤#1:创建一个正方形div,添加一个具有完全相同尺寸的伪(或子),并background-image在该伪上设置 。

\n
div {\n    display: grid; /* makes pseudo stretch all across */\n    width: 28em; /* whatever edge value we want */\n    aspect-ratio: 1; /* make it square */\n    /* just to highlight div boundaries */\n    box-shadow: 0 0 0 3px;\n    \n    &::after {\n        background: url(image.jpg) 50%/ cover;\n        content: \'\'\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

步骤#2:将transform-origin伪值设置为底部边缘的中间 ( 100% 50%) - 这可确保底部边缘在应用 3D 变换后保持在原位。

\n

步骤#3:沿轴应用 3D 倾斜z,沿轴延长边缘y

\n

是的,我们在 CSS 中没有 3D 倾斜函数。但是我们有matrix3d(),它可以用来表达任何旋转、缩放、倾斜、平移!

\n

因此,让我们首先了解倾斜是如何工作的。

\n

倾斜沿轴发生。

\n

这是一个交互式演示,说明了 2D 倾斜函数如何工作。

\n

考虑这个例子,我们沿着x轴倾斜,并且当y轴旋转远离其初始位置时,沿着 y 轴的边缘变长 - 这个角度就是倾斜角。z轴垂直于我们倾斜的平面(本例中为xOy)并且不受影响:

\n

skewX 示例

\n

好吧,在我们的例子中,我们做了类似的事情,但是倾斜发生在yOz平面,而不是xOy平面,因为我们沿着z轴而不是沿着x轴倾斜。

\n

坐标系

\n

Since we\'ve anchored the middle of the bottom edge of our pseudo in place with transform-origin and this skew happens along the z axis (perpendicular onto the screen), it results we\'re basically pulling and stretching our pseudo back, towards the back of the screen, preserving the x and y coordinates of every point, but changing the z coordinates.

\n

Basically, it would look like below if we were to view it in 3D without flattening into the parent\'s plane (the parent is bounded by the outline).

\n

结果 z 倾斜后的 3D 视图没有展平到父级平面

\n

You can see how the horizontal guidelines at the top show how the top of the skewed pseudo has preserved its x and y coordinates, it just got pulled back along the z axis.

\n

Alright, how do we CSS this?

\n

As mentioned, there\'s no 3D skew, but we can build our transform matrix ourselves. Since this is a skew along the z axis (3rd axis) stretching the edge along the y axis (2nd axis), the only position in our matrix different from the unit matrix (1 along the main diagonal, 0 elsewhere) is going to be on the 3rd row, 2nd column. And we\'re going to have the tangent of the skew angle there. On MDN, you can see this for skewX() and skewY() too.

\n

This is because every point along the skew axis gets displaced by its coordinate along the lengthening axis times the tangent of the skew angle - you can see this in the first illustration if you draw parallels to the axes (x axis, y axis pre- and post-skew) through the example point in its original position (in grey) and final position (in black). Drawing these parallels creates a right triangle where the x displacement over the y coordinate is the tangent of the skew angle.

\n

Okay, back to the matrix, it looks like this.

\n
1   0    0\n0   1    0\n0 tan(a) 1\n
Run Code Online (Sandbox Code Playgroud)\n

To get the matrix3d() values, we add one more row and one more column identical to what they\'d be in a 4x4 unit matrix and then just list the values column by column (not row by row!). So far, we have:

\n
@use \'sass:math\'; // allows us to use trigonometric functions\n$a: 60deg; // the skew angle\n\ndiv {\n    display: grid;\n    width: 28em;\n    aspect-ratio: 1;\n    perspective: 25em;\n    box-shadow: 0 0 0 3px;\n    \n    &::after {\n        transform-origin: 50% 100%;\n        transform: matrix3d(1, 0, 0, 0, /* 1st column */\n                            0, 1, math.tan($a), 0, /* 2nd column */\n                            0, 0, 1, 0, /* 3rd column */\n                            0, 0, 0, 1);\n        background: url(image.jpg) 50%/ cover;\n        content: \'\'\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Note we\'ve also added a perspective to get the distorted view (smaller at the top/ further back).

\n

The code so far gives us the flattened version of what we can see in the gif above. And I say the flattened version because, with what we have here, the pseudo always gets flattened in the plane of its parent.

\n

When the parent div has no 3D transform, we look at it from the front and the pseudo obviously looks flattened.

\n

When the parent div does have a 3D transform, its 3D-transformed pseudo gets flattened into its plane because the default transform-style value is flat. This means that any 3D-transformed children/ pseudos of a 3D transformed parent get flattened in the plane of the parent. This can be changed if we set the div\'s transform-style to preserve-3d. But we don\'t want that here.

\n

Step 4: fix the top edge!

\n

There\'s just one more thing that still doesn\'t look right: the post-transform top edge is now below the original one.

\n

转换后结果

\n

This is because we\'ve set a perspective and how this works. By default, the perspective-origin is dead in the middle of the element we set it on (in this case our div), at 50% horizontally and 50% vertically.

\n

Let\'s consider just the points behind the plane of the screen because that\'s where our entire 3D-skewed pseudo is.

\n

With the default perspective-origin (50% 50%), only the points on the line perpendicular onto the plane of the screen in the very middle of our div are going to be projected onto the screen plane at a point with the same x,y coordinates as their own after taking into account perspective. Only the points in the plane perpendicular onto the screen and intersecting the screen along the horizontal midline of the div are going to be projected onto this horizontal midline after taking into account perspective.

\n

Do you see where this is going? If we move the perspective-origin so that it\'s in the middle of the div\'s top edge (50% 0), then the points in the plane perpendicular onto the screen along this top edge are going to be projected along this top edge - that is, the top edge of the 3D-skewed pseudo will be along the same line as its parent\'s top edge.

\n

So our final code is:

\n
@use \'sass:math\'; // allows us to use trigonometric functions\n$a: 60deg; // the skew angle\n\ndiv {\n    display: grid;\n    width: 28em;\n    aspect-ratio: 1;\n    perspective-origin: 50% 0;\n    perspective: 25em;\n    box-shadow: 0 0 0 3px;\n    \n    &::after {\n        transform-origin: 50% 100%;\n        transform: matrix3d(1, 0, 0, 0, /* 1st column */\n                            0, 1, math.tan($a), 0, /* 2nd column */\n                            0, 0, 1, 0, /* 3rd column */\n                            0, 0, 0, 1);\n        background: url(image.jpg) 50%/ cover;\n        content: \'\'\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Here is a live comparative view between our result and its pre-transform version as both divs rotate in 3D to show they\'re flat in the xOy plane.

\n

比较视图动画

\n
\n

Don\'t want to use a preprocessor for the tangent value? Firefox and Safari support trigonometric functions by default already and Chrome 111+ supports them with the Experimental Web Platform features flag enabled in chrome://flags.

\n

Edit: trigonometric functions in CSS now work cross-browser.

\n

Don\'t want to wait for Chromium support either? You don\'t even need to use a tangent computation there, you can use any positive number - the bigger this number gets, the smaller the top edge gets. I used the tangent value to illustrate where it comes from, but you don\'t have to. Our tangent values are computed for angles from 0\xc2\xb0 to 90\xc2\xb0. This gives us tangent values from 0 to +Infinity. So yeah, any positive number will do there in the matrix.

\n