css3 from square image to trapezoid without x rotation
P粉604669414
P粉604669414 2024-03-28 15:26:58
0
1
348

I'm trying to convert a square div with a background image to a trapezoid.

I want to make it in 2D, much the same way as Photoshop's "distort" tool.

Basically, all I want is to shrink the top edge of the square and have the image deform accordingly.

3D conversion "seems" to do the trick:

transform: rotateX(30deg);

It works for most use cases, but not all. In fact, it is a 30 degree rotated square that "looks" like a trapezoid when viewed from the front/back, but is still a 30 degree rotated square when viewed from any other side.

What I want is to get a true trapezoid. I want the square image to be distorted in a 2D manner so that the shape and image actually change without involving rotation.

I tried this and it works in terms of shape (trapezoid):

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

But I can't replace the red area with the distorted background image. This defeats my purpose. Everything I try keeps the image undistorted.

Are there any css/html5/javascript tricks to achieve what I want?

Thanks.

P粉604669414
P粉604669414

reply all(1)
P粉131455722

You can get the effect by applying a 3D transform to the pseudo-element (you can also set a background-image on it) and making sure it is flattened on its original plane (the plane of its parent element) . This means that if you want to rotate something in 3D, you have to rotate the parent object.

Step #1: Create a square div, add a dummy variable (or subvariable) with the exact same dimensions, and set the background on that dummy variable -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 dummy value to the middle of the bottom edge (100% 50%) - this ensures that the The bottom edge of the 3D back remains transformed in place.

Step #3: Apply 3D Bevel along the z axis, extending the edge along the y axis.

Yes, we don’t have a 3D tilt function in CSS. But we have matrix3d(), which can be used to express any rotation, scaling, tilt, and translation!

So let’s first understand how tilt works.

Tilt occurs along the axis.

This is an interactive demonstration illustrating how the 2D tilt function works.

Consider this example, we are tilting along the x axis, as the y axis rotates away from its initial position, the edges along the y axis are elongated - this angle is the tilt angle . z The axis is perpendicular to the plane we are tilting (xOy in this case) and is unaffected:

Well, in our case we did something similar, but the tilt happened in the yOz plane instead of the xOy plane, since we were along the yOz The plane is tilted >z axis instead of along the x axis.

Since we have anchored the middle of the bottom edge of the dummy value in place using transform-origin, and this tilt occurs along the z axis (perpendicular to the screen), we Basically pulling our pseudo back towards the back of the screen, keeping the x and y coordinates of each point, but changing the z coordinates.

Basically, if we view it in 3D without flattening to the parent plane (the parent is limited by the outline), it will look like this.

You can see how the horizontal guideline at the top shows how the top of the tilted pseudo value retains its x and y coordinates, it's just along the z axis .

Okay, how do we do that using CSS?

As mentioned above, there is no 3D tilt, but we can build the transformation matrix ourselves. Since this is a tilt along the z axis (the third axis) and a stretch along the y axis (the second axis), the only thing in the matrix that is the same as the identity matrix (1 along the main diagonal, 0 elsewhere) will be on row 3 of the 2nd column. We're going to get the tangent of the tilt angle there. On MDN, you can also see this in skewX() and skewY().

This is because every point along the tilt axis is displaced by its coordinate along the elongated axis times the tangent of the tilt angle - you can see this in the first illustration if you draw a line parallel to the axis This point (x axis, y axis (front and rear tilt) passes through the original position (grey) and final position (black) of the example point. Drawing these parallel lines creates a right triangle, Among them, the x displacement on the y coordinate is the tangent of the tilt angle.

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 column, the same as those in the 4x4 identity matrix, and then list the values ​​column by column ( is not ) > Line by line!). So far we have:

@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 that we also added perspective to get a distorted view (smaller at the top/further back).

The code so far gives us a flattened version of what we see in the gif above. I say flattened version because, as far as we have here, pseudo-objects are always flattened on the plane of their parent.

When the parent div has no 3D transformation, when we look at it from the front, the pseudo-obvious look is flat.

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

Step 4: Pin the top edge!

One more thing that still doesn't look right: transform The top edge is now below the original edge.

This is because we set the perspective and how it works. By default, perspective-origin dies in the middle of the element we set it on (in this case our div), with a horizontal orientation of 50% , the vertical direction is 50%.

Let's only consider points behind the screen plane, since that's where our entire 3D tilt pseudo-value is.

Using the default perspective-origin (50% 50%), only the points on the line perpendicular to the screen plane in the middle of div will be Projected to a point on the screen plane: the same x,y coordinates as your own, taking into account the viewing angle. After perspective is taken into account, only points in the plane that are perpendicular to the screen and intersect the screen along the horizontal centerline of div are projected onto that horizontal centerline.

Do you know what's going on? If we move the perspective-origin so that it is in the middle of the div's top edge (50% 0), then points in a plane perpendicular to the screen along that top edge will be along that Top edge projection - that is, the top edge of the 3D tilted pseudo-object will be in line with the top edge of its parent.

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: ''
    }
}

This is a live comparison view between our result and its pre-transformed version Because both divs are rotated in 3D to show that they are flat on the xOy plane .


Don't want to use a preprocessor to handle tangent values? Firefox and Safari already support trigonometric functions by default, and Chrome 111 supports trigonometric functions by enabling the Experimental Web Platform Features flag in chrome://flags.

Don’t want to wait for Chromium support? You don't even need to use the tangent calculation there, you can use any positive number - the larger the number, the smaller the top edge will be. I use the tangent value to illustrate where it comes from, but you don't have to. Our tangent is calculated for angles from to 90°. This gives us the tangent value from 0 to Infinity. So yes, any positive number can appear in a matrix.

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template