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.
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 thebackground on that dummy variable -image
.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 they
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 inskewX()
andskewY()
.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.
To get the
matrix3d()
values, we add one more row and column, the same as those in the4x4
identity matrix, and then list the values column by column ( is not ) > Line by line!). So far we have: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 defaulttransform-style
value isflat
. 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'stransform-style
topreserve-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 ourdiv
), with a horizontal orientation of50%
, the vertical direction is50%
.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 ofdiv
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 ofdiv
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:
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
0°
to90°
. This gives us the tangent value from0
toInfinity
. So yes, any positive number can appear in a matrix.