简体   繁体   English

css3 从方形图像到没有 x 旋转的梯形

[英]css3 from squared image to trapezoid without x rotation

I'm trying to transform a square div with a background image to a trapezoid.我正在尝试将带有背景图像的方形 div 转换为梯形。

I would like to make it in 2D, pretty much the same way the "Distort" tool of Photoshop does.我想以 2D 的方式制作它,就像 Photoshop 的“扭曲”工具所做的一样。

Basically, all I want is to narrow the top side of the square and get the image to be deformed accordingly.基本上,我想要的只是缩小正方形的顶部并使图像相应地变形。

The 3D transformation "seems" to do the trick: 3D 转换“似乎”可以解决问题:

transform: rotateX(30deg);

It works for most use cases but not all of them.它适用于大多数用例,但不是所有用例。 Indeed, It is a 30deg rotation of the square that "looks" like a trapezoid when seen from the front/back side but is remains a 30° rotated square when seen from any other side.事实上,它是正方形旋转 30 度,从正面/背面看时“看起来”像一个梯形,但从任何其他侧面看仍然是旋转 30 度的正方形。

What I want is to get an actual trapezoid.我想要的是得到一个真正的梯形。 I want the squared image to be distorted in a 2D way so that the shape and image are actually changed, with no rotation involved.我希望以 2D 方式扭曲方形图像,以便实际改变形状和图像,而不涉及旋转。

I tried this and it worked in terms of shape (trapezoid):我尝试了这个并且它在形状(梯形)方面起作用:

border-style: solid;
height: 0;
border-color: transparent transparent red transparent;
border-width: 0 100px 100px 100px;

But then I can't replace the red area with a background-image that would follow the distortion.但是我不能用会跟随失真的背景图像替换红色区域。 Which defeats my purpose.这违背了我的目的。 Any attempt I tried gets the picture to remain undeformed.我尝试过的任何尝试都会使图片保持不变形。

Is there any css/html5/javascript trick that would achieve what I want?是否有任何 css/html5/javascript 技巧可以实现我想要的?

Thanks.谢谢。

You can get the effect by applying a 3D transform on a pseudo-element (on which you also set the background-image ) and making sure it's flattened in its original plane - that of its parent.您可以通过对伪元素(您还设置了background-image )应用 3D 变换并确保它在其原始平面(其父元素的平面)中展平来获得效果。 This means that if you want to rotate something in 3D, you have to rotate the parent.这意味着如果你想旋转 3D 中的东西,你必须旋转父级。

Step #1 : create a square div , add a pseudo (or a child) that has the exact same dimensions and set the background-image on this pseudo.步骤#1 :创建一个正方形div ,添加一个具有完全相同尺寸的伪(或子)并在此伪上设置background-image

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

Step #2 : set the transform-origin on the pseudo to the middle of the bottom edge ( 100% 50% ) - this ensures the bottom edge will remain in place after applying the 3D transform.第 2 步:将伪上的transform-origin设置为底部边缘的中间 ( 100% 50% ) - 这确保在应用 3D 变换后底部边缘将保持在原位。

Step #3 : apply a 3D skew along the z axis lengthening the edge along the y axis.步骤#3 :沿z y延长边缘。

Yes, we don't have 3D skew functions in CSS. But we have matrix3d() , which can be used to express any rotation, scale, skew, translation!是的,我们在CSS中没有3D个倾斜函数。但是我们有matrix3d() ,可以用来表示任何旋转,缩放,倾斜,平移!

So let's first understand how skewing works.因此,让我们首先了解倾斜是如何工作的。

Skewing happens along an axis.倾斜发生在一个轴上。

Here's an interactive demo illustrating how the 2D skew functions work.这是一个交互式演示,说明 2D 倾斜函数的工作原理。

Consider this example, where we skew along the x axis and the edge along the y axis gets lengthened as the y axis rotates away from its initial position - this angle is the skew angle.考虑这个例子,我们沿x轴倾斜,随着 y 轴旋转远离其初始 position,沿y轴的边缘变长 - 这个角度是倾斜角。 The z axis is perpendicular onto the plane in which we skew ( xOy in this example) and is unaffected: z轴垂直于我们倾斜的平面(本例中为xOy )并且不受影响:

skewX 示例

Well, in our case, we do something similar, but the skew happens in the yOz plane, not in the xOy plane, as we skew along the z axis instead of along the x axis.嗯,在我们的例子中,我们做了类似的事情,但是倾斜发生在yOz平面,而不是xOy平面,因为我们沿着z轴而不是沿着x轴倾斜。

坐标系

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.因为我们已经用transform-origin锚定了我们的 pseudo 底部边缘的中间,并且这种倾斜发生在z轴上(垂直于屏幕),结果我们基本上是在向后拉和拉伸我们的 pseudo 背部,朝向屏幕背面,保留每个点的xy坐标,但更改z坐标。

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).基本上,如果我们在 3D 中查看它而不展平到父平面(父平面以轮廓为界),它看起来会像下面这样。

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

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.您可以看到顶部的水平参考线如何显示倾斜伪模型的顶部如何保留其xy坐标,它只是沿z轴拉回。

Alright, how do we CSS this?好吧,我们CSS这个怎么办?

As mentioned, there's no 3D skew, but we can build our transform matrix ourselves.如前所述,没有 3D 偏斜,但我们可以自己构建变换矩阵。 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.由于这是沿z轴(第 3 轴)的倾斜,沿y轴(第 2 轴)拉伸边缘,因此矩阵中唯一与单位矩阵不同的 position(主对角线上为1 ,其他位置为0 )将是在第 3 行,第 2 列。 And we're going to have the tangent of the skew angle there.我们将在那里获得倾斜角的切线。 On MDN, you can see this for skewX() and skewY() too.在 MDN 上,您也可以在skewX()skewY()中看到这一点。

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).这是因为沿倾斜轴的每个点都会被其沿延长轴的坐标乘以倾斜角的切线而移位 - 如果您绘制平行于轴( x轴, y轴前和后倾斜)通过其原始 position(灰色)和最终 position(黑色)中的示例点。 Drawing these parallels creates a right triangle where the x displacement over the y coordinate is the tangent of the skew angle.绘制这些平行线会创建一个直角三角形,其中y坐标上的x位移是倾斜角的正切。

Okay, back to the matrix, it looks like this.好的,回到矩阵,它看起来像这样。

1   0    0
0   1    0
0 tan(a) 1

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:要获得matrix3d()值,我们再添加一行和一列,与它们在4x4单位矩阵中的内容相同,然后逐列(而不是逐行)列出值,到目前为止:我们有:

@use 'sass:math'; // allows us to use trigonometric functions
$a: 60deg; // the skew angle

div {
    display: grid;
    width: 28em;
    aspect-ratio: 1;
    perspective: 25em;
    box-shadow: 0 0 0 3px;
    
    &::after {
        transform-origin: 50% 100%;
        transform: matrix3d(1, 0, 0, 0, /* 1st column */
                            0, 1, math.tan($a), 0, /* 2nd column */
                            0, 0, 1, 0, /* 3rd column */
                            0, 0, 0, 1);
        background: url(image.jpg) 50%/ cover;
        content: ''
    }
}

Note we've also added a perspective to get the distorted view (smaller at the top/ further back).请注意,我们还添加了一个perspective来获得扭曲的视图(顶部较小/更靠后)。

The code so far gives us the flattened version of what we can see in the gif above.到目前为止的代码为我们提供了我们在上面的 gif 中看到的扁平化版本。 And I say the flattened version because, with what we have here, the pseudo always gets flattened in the plane of its parent.我说扁平化版本是因为,根据我们这里的内容,伪类总是在其父级平面中被扁平化。

When the parent div has no 3D transform, we look at it from the front and the pseudo obviously looks flattened.当parent div没有3D变换的时候,我们从正面看,pseudo明显看起来扁平化了。

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 .当父div确实有一个 3D 变换时,它的 3D 变换伪会被展平到它的平面中,因为默认的transform-style值是flat This means that any 3D-transformed children/ pseudos of a 3D transformed parent get flattened in the plane of the parent.这意味着 3D 转换父对象的任何 3D 转换子对象/伪对象在父对象的平面中被展平。 This can be changed if we set the div's transform-style to preserve-3d .如果我们将 div 的transform-style设置为preserve-3d ,这可以改变。 But we don't want that here.但我们不想在这里。

Step 4 : fix the top edge!第 4 步:修复顶部边缘!

There's just one more thing that still doesn't look right: the post- transform top edge is now below the original one.还有一件事看起来仍然不对: transform后的上边缘现在低于原始边缘。

转换后结果

This is because we've set a perspective and how this works.这是因为我们已经设定了一个perspective及其运作方式。 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.默认情况下, perspective-origin位于我们设置它的元素的中间(在本例中为我们的div ),水平方向为50% 50% ,垂直方向为 50%。

Let's consider just the points behind the plane of the screen because that's where our entire 3D-skewed pseudo is.让我们只考虑屏幕平面后面的点,因为那是我们整个 3D 倾斜伪像所在的位置。

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.使用默认的perspective-origin ( 50% 50% ),只有位于div正中间的垂直于屏幕平面的直线上的点将在具有相同x 的点处投影到屏幕平面上,考虑到透视后, y坐标为自己的坐标。 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.考虑到透视后,只有垂直于屏幕并沿div的水平中线与屏幕相交的平面中的点将被投影到这条水平中线上。

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.如果我们移动perspective-origin使其位于 div 顶部边缘的中间 ( 50% 0 ),那么平面中沿该顶部边缘垂直于屏幕的点将沿着该顶部边缘投影 - 即, 3D 倾斜伪对象的上边缘将与其父对象的上边缘在同一条线上。

So our final code is:所以我们最终的代码是:

@use 'sass:math'; // allows us to use trigonometric functions
$a: 60deg; // the skew angle

div {
    display: grid;
    width: 28em;
    aspect-ratio: 1;
    perspective-origin: 50% 0;
    perspective: 25em;
    box-shadow: 0 0 0 3px;
    
    &::after {
        transform-origin: 50% 100%;
        transform: matrix3d(1, 0, 0, 0, /* 1st column */
                            0, 1, math.tan($a), 0, /* 2nd column */
                            0, 0, 1, 0, /* 3rd column */
                            0, 0, 0, 1);
        background: url(image.jpg) 50%/ cover;
        content: ''
    }
}

Here is a live comparative view between of result and its pre-transforms version as both divs rotate in 3D to show they're flat in the xOy plane.这是结果与其预转换版本之间的实时比较视图,因为两个 div 在 3D 中旋转以显示它们在xOy平面中是平坦的。

比较视图动画


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 . Firefox 和 Safari 默认已经支持三角函数,Chrome 111+ 通过在chrome://flags中启用的实验性 Web 平台功能标志支持它们。

Don't want to wait for Chromium support either?也不想等待 Chromium 支持? 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 to 90° .我们的切线值是针对90°的角度计算的。 This gives us tangent values from 0 to +Infinity .这给了我们从0+Infinity的切线值。 So yeah, any positive number will do there in the matrix.所以是的,矩阵中的任何正数都可以。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM