Sliding 3D Image Frames In CSS<\/h1>\nTemani Afif<\/address>\n 2024-04-12T18:00:00+00:00
\n 2025-03-19T12:04:52+00:00
\n <\/header>\n
In a previous article<\/a>, we played with CSS masks to create cool hover effects where the main challenge was to rely only on the
<\/code> tag as our markup. In this article, pick up where we left off by \u201crevealing\u201d the image from behind a sliding door sort of thing \u2014 like opening up a box and finding a photograph in it.<\/p>\n\nSee the Pen [Image gift box (hover to reveal)](https:\/\/codepen.io\/smashingmag\/pen\/LYaPPPo) by Temani Afif<\/a>.<\/p>See the Pen Image gift box (hover to reveal)<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nPretty neat, right? You might think this is an easy thing to pull off. All we really need is an overlay above the image that we translate, and, boom, we\u2019re done, right?<\/p>\n
That\u2019s true. But if you check the code, you won\u2019t find any additional elements in the markup other than the exact same
<\/code> tag we used last time. Plus, we cannot even use pseudo-elements to make this work. This is what makes such an effect a bit more challenging.<\/p>\nDon\u2019t look at the code right now. Let\u2019s build it together by breaking the demo into isolated little CSS tricks.<\/p>\n
The Image And Sliding Overlay<\/h2>\n
You would be correct in thinking it\u2019s impossible to add an overlay to an image without an extra element. Instead, we are going to fake it and create the illusion of an overlay.<\/p>\n
Let\u2019s start with the following code:<\/p>\n
img {\n --s: 200px; \/* the image size *\/\n \n width: var(--s);\n box-sizing: border-box;\n padding-right: var(--s);\n background: #8A9B0F;\n transition: 1s;\n}\nimg:hover {\n padding: 0;\n}\n<\/code><\/pre>\nWe have defined the width<\/code> as a CSS variable (--s<\/code>) and repurposed it to apply padding along the right side of the element. Combined with box-sizing: border-box<\/code>, this will make the size of the content box equal to 0<\/code>. In other words, we don\u2019t see the image, but we see the background color since it covers the padding area.<\/p>\nOn hover, let\u2019s make the padding equal to 0<\/code>:<\/p>\n\nSee the Pen [Padding animation to reveal the image](https:\/\/codepen.io\/smashingmag\/pen\/jOJrqXo) by Temani Afif<\/a>.<\/p>See the Pen Padding animation to reveal the image<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nNothing surprising, right? By decreasing<\/em> the padding, we increase<\/em> the size of the content box and it slowly reveals the image. We\u2019re basically squishing it vertically and allowing to widen back into place on hover.<\/p>\nLet\u2019s add two more properties to the mix:<\/p>\n
img {\n object-fit: cover;\n object-position: left;\n}\n<\/code><\/pre>\n\nSee the Pen [Adding object-* properties](https:\/\/codepen.io\/smashingmag\/pen\/oNVLLdM) by Temani Afif<\/a>.<\/p>See the Pen Adding object-* properties<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nTada!<\/em> The effect looks much better now, and we have an overlay reveal animation even if, in reality, the overlay you see is the background, which is behind the image! The illusion is perfect.<\/p>\nWhy does it behave like that? The logic is explained nicely over at MDN<\/a>:<\/p>\n\u201cThe replaced content is sized to maintain its aspect ratio while filling the element\u2019s entire content box. If the object\u2019s aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit.\u201d<\/p><\/blockquote>\n
In other words, the image will maintain its ratio while filling the content box. As a result, the image does not get distorted by the padding as we saw in the first demo \u2014 instead, it is clipped. Then, object-position: left<\/code> aligns the position of the image to the left so it doesn\u2019t move while the size of the content box increases as a result of the decreased padding on hover.<\/p>\nIf we change the position to right<\/code>, you get a different effect:<\/p>\n\nSee the Pen [Using object-position: right](https:\/\/codepen.io\/smashingmag\/pen\/xxBOOJW) by Temani Afif<\/a>.<\/p>See the Pen Using object-position: right<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nInstead of an overlay animation, we have a kind of sliding effect where the image enters from the left. This is directly related to another cool CSS trick that I used in a previous article<\/a> to create a \u201cpop-out\u201d hover effect:<\/p>\n\nSee the Pen [Fancy Pop Out Reveal hover effect!](https:\/\/codepen.io\/smashingmag\/pen\/VwqWRyj) by Temani Afif<\/a>.<\/p>See the Pen Fancy Pop Out Reveal hover effect!<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nFor this article, we are going to rely on the first effect, where the image remains fixed. Here is a demo with all the sliding variations:<\/p>\n\nSee the Pen [A reveal hover effect with one element](https:\/\/codepen.io\/smashingmag\/pen\/GRYEZrr) by Temani Afif<\/a>.<\/p>See the Pen A reveal hover effect with one element<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nYou will notice that it\u2019s pretty easy to switch between the different variations by toggling a couple of values in the CSS.<\/p>\n
Sliding The Overlay Outside The Image<\/h2>\n
Now that we have our overlay, let\u2019s try to slide it outside of the image. Instead of decreasing its size like we did previously, we want it to maintain its size and move it.<\/p>\n
For this, let\u2019s use a box-shadow<\/code> animation:<\/p>\nimg {\n --s: 200px; \/* the image size *\/\n\n box-shadow: 0 0 #8A9B0F; \n}\nimg:hover {\n box-shadow: var(--s) 0 #8A9B0F;\n}\n<\/code><\/pre>\n\nSee the Pen [Adding box-shadow animation](https:\/\/codepen.io\/smashingmag\/pen\/KKEMvNq) by Temani Afif<\/a>.<\/p>See the Pen Adding box-shadow animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nCool, right? We have an overlay above our image that slides over to reveal the image \u2014 without using any extra elements in the markup or pseudo-elements in the styles!<\/p>\n
We can do the same effect using a clip-path<\/code> animation as well.<\/p>\nimg {\n --s: 200px; \/* the image size *\/\n\n box-shadow: 0 0 0 999px #8A9B0F; \n clip-path: inset(0 0 0 0);\n}\nimg:hover {\n clip-path: inset(0 -100% 0 0);\n}\n<\/code><\/pre>\nWe define a box-shadow<\/code> as having a widespread radius, but we won\u2019t actually see it because it\u2019s clipped. On hover, though, we update the inset()<\/code> value to reveal the box-shadow<\/code> on the right side of the image.<\/p>\n\nSee the Pen [Using clip-path instead of box-shadow](https:\/\/codepen.io\/smashingmag\/pen\/YzgWxxK) by Temani Afif<\/a>.<\/p>See the Pen Using clip-path instead of box-shadow<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nUsing the same technique, we can slide the overlay in whatever direction we want. Can you figure out how? Give it a shot by forking the Pen above and changing directions as an exercise before we move to the next part of our work.<\/p>\n
Adding Borders<\/h2>\n
Borders can help create space around the image and get it close to a square box shape. Don\u2019t forget that we want to create a 3D box in the end. But let\u2019s see what happens when we add borders.<\/p>\n\nSee the Pen [Adding border](https:\/\/codepen.io\/smashingmag\/pen\/YzgWrvQ) by Temani Afif<\/a>.<\/p>See the Pen Adding border<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nHmm, not good. The border sits above the overlay, and the image isn\u2019t a perfect square, at least initially. Even if that seems glitchy at first, it\u2019s a logical outcome since the border is painted above the background, and its thickness adds up to the element\u2019s total size.<\/p>\n
What we need to do is adjust the padding to account for the border\u2019s size. Then, let\u2019s make the border transparent so that we can see the background color behind it.<\/p>\n
img {\n --s: 200px; \/* the image size *\/\n --b: 10px; \/* border width *\/\n --c: #8A9B0F;\n \n width: var(--s);\n aspect-ratio: 1;\n box-sizing: border-box;\n padding-top: calc(var(--s) - 2*var(--b));\n border: var(--b) solid #0000;\n box-shadow: 0 0 0 999px var(--c); \n background: var(--c);\n clip-path: inset(0);\n object-fit: cover;\n object-position: bottom;\n}\nimg:hover {\n padding: 0;\n clip-path: inset(-100% 0 0);\n}\n<\/code><\/pre>\n\nSee the Pen [Fixing the border issue](https:\/\/codepen.io\/smashingmag\/pen\/mdoEBQX) by Temani Afif<\/a>.<\/p>See the Pen Fixing the border issue<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nThis looks a lot better. It would be even better if we were to use a different color for the border area. Let\u2019s consider using multiple<\/em> backgrounds.<\/p>\nimg {\n --c: #8A9B0F;\n --_c: color-mix(in srgb, var(--c), #fff 25%);\n \n background:\n linear-gradient(var(--_c) 0 0) no-repeat\n 0 0 \/ 100% 100%,\n var(--c);\n background-origin: border-box;\n box-shadow: 0 0 0 999px var(--_c);\n \/* same as previous *\/\n}\nimg:hover {\n background-size: 100% 0%;\n \/* same as previous *\/\n}\n<\/code><\/pre>\nFirst off, note that we\u2019ve added the color-mix()<\/code> function that allows us to define a new color variation from the original color value (--c:<\/code> #8A9B0F<\/code>) by mixing it with white to get a brighter shade. Then, we use that new color to create a gradient above the element\u2019s background color, which is declared right after the gradient. The same color is also used for the box-shadow<\/code>.<\/p>\nThe idea is to decrease the size of the gradient the same way we do with the padding so that the background-color<\/code> behind the gradient is revealed.<\/p>\n\nSee the Pen [Adding gradient animation](https:\/\/codepen.io\/smashingmag\/pen\/rNRLJpB) by Temani Afif<\/a>.<\/p>See the Pen Adding gradient animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nThat\u2019s really nice! But did you catch the subtle visual issue? If you look closely, you can notice that the overlay is slightly out of alignment with the border.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nThis is because the padding has a transition that goes from s - 2*b<\/code> to 0<\/code>. Meanwhile, the background transitions from 100%<\/code> (equivalent to --s<\/code>) to 0<\/code>. There\u2019s a difference equal to 2*b<\/code>. The background covers the entire area, while the padding covers less of it. We need to account for this.<\/p>\nIdeally, the padding transition would take less time to complete and have a small delay at the beginning to sync things up, but finding the correct timing won\u2019t be an easy task. Instead, let\u2019s increase the padding transition\u2019s range to make it equal to the background.<\/p>\n
img {\n --h: calc(var(--s) - var(--b));\n padding-top: min(var(--h), var(--s) - 2*var(--b));\n transition: --h 1s linear;\n}\nimg:hover {\n --h: calc(-1 * var(--b));\n}\n<\/code><\/pre>\nThe new variable, --h<\/code>, transitions from s - b<\/code> to -b<\/code> on hover, so we have the needed range since the difference is equal to --s<\/code>, making it equal to the background<\/code> and clip-path<\/code> transitions.<\/p>\nThe trick is the min()<\/code> function. When --h<\/code> transitions from s - b<\/code> to s - 2*b<\/code>, the padding is equal to s - 2*b<\/code>. No padding changes during that brief transition. Then, when --h<\/code> reaches 0<\/code> and transitions from 0<\/code> to -b<\/code>, the padding remains equal to 0<\/code> since, by default, it cannot be a negative value.<\/p>\nIt would be more intuitive to use clamp()<\/code> instead:<\/p>\npadding-top: clamp(0px, var(--h), var(--s) - 2*var(--b));\n<\/code><\/pre>\nThat said, we don\u2019t need to specify the lower parameter since padding cannot be negative and will, by default, be clamped to 0<\/code> if you give it a negative value.<\/p>\nWe are getting much closer to the final result!<\/p>\n\nSee the Pen [Fixing the padding issue](https:\/\/codepen.io\/smashingmag\/pen\/ExMgZbW) by Temani Afif<\/a>.<\/p>See the Pen Fixing the padding issue<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nWorth noting that we need to use @property<\/code> to be able to apply a transition to the --h<\/code> variable. The transition won\u2019t work in Firefox at the time of this writing.<\/p>\nThe 3D Effect<\/h2>\n
The last step is to add a touch of 3D to the effect. To better understand how we\u2019re going to approach this, let\u2019s temporarily remove the box-shadow<\/code>, clip-path<\/code>, and the linear-gradient()<\/code> with the image in its revealed state.<\/p>\n\nSee the Pen [The revealed image with border](https:\/\/codepen.io\/smashingmag\/pen\/QWoKpyE) by Temani Afif<\/a>.<\/p>See the Pen The revealed image with border<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nWe\u2019ll take three steps to create the 3D effect I have mapped out in the following figure.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nFirst, we increase the border\u2019s thickness on the left and bottom sides of the image:<\/p>\n
\nimg {\n --b: 10px; \/* the image border *\/\n --d: 30px; \/* the depth *\/\n\n border: solid #0000;\n border-width: var(--b) var(--b) calc(var(--b) + var(--d)) calc(var(--b) + var(--d));\n}\n<\/code><\/pre>\n<\/div>\nSecond, we add a conic-gradient()<\/code> on the background to create darker colors around the box:<\/p>\n\nbackground: \n conic-gradient(at left var(--d) bottom var(--d),\n #0000 25%,#0008 0 62.5%,#0004 0) \n var(--c);\n<\/code><\/pre>\n<\/div>\nNotice the semi-transparent black color values (e.g., #0008<\/code> and #0004<\/code>). The slight bit of transparency blends with the colors behind it to create the illusion of a dark variation of the main color since the gradient is placed above the background color.<\/p>\nAnd lastly, we apply a clip-path<\/code> to cut out the corners that establish the 3D box.<\/p>\n\nclip-path: polygon(var(--d) 0, 100% 0, 100% calc(100% - var(--d)), calc(100% - var(--d)) 100%, 0 100%, 0 var(--d));\n<\/code><\/pre>\n<\/div>\n\nSee the Pen [The image within a 3D box](https:\/\/codepen.io\/smashingmag\/pen\/JjzRWXZ) by Temani Afif<\/a>.<\/p>See the Pen The image within a 3D box<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nNow that we see and understand how the 3D effect is built let\u2019s put back the things we removed earlier, starting with the padding:<\/p>\n\nSee the Pen [Putting back the padding animation](https:\/\/codepen.io\/smashingmag\/pen\/ExMgWXR) by Temani Afif<\/a>.<\/p>See the Pen Putting back the padding animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nIt works fine. But note how we\u2019ve introduced the depth (--d<\/code>) to the formula. That\u2019s because the bottom border is no longer equal to b<\/code> but b + d<\/code>.<\/p>\n\n--h: calc(var(--s) - var(--b) - var(--d));\npadding-top: min(var(--h),var(--s) - 2*var(--b) - var(--d));\n<\/code><\/pre>\n<\/div>\nLet\u2019s do the same thing with the linear gradient. We need to decrease its size so it covers the same area as it did before we introduced the depth so that it doesn\u2019t overlap with the conic gradient:<\/p>\n\nSee the Pen [Putting back the gradient animation](https:\/\/codepen.io\/smashingmag\/pen\/VwRKpzN) by Temani Afif<\/a>.<\/p>See the Pen Putting back the gradient animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nWe are getting closer! The last piece we need to add back in from earlier is the clip-path<\/code> transition that is combined with the box-shadow<\/code>. We cannot reuse the same code we used before since we changed the clip-path<\/code> value to create the 3D box shape. But we can still transition it to get the sliding result we want.<\/p>\nThe idea is to have two points at the top that move up and down to reveal and hide the box-shadow<\/code> while the other points remain fixed. Here is a small video to illustrate the movement of the points.<\/p>\n\n
\n 2025-03-19T12:04:52+00:00
\n <\/header>\n
<\/code> tag as our markup. In this article, pick up where we left off by \u201crevealing\u201d the image from behind a sliding door sort of thing \u2014 like opening up a box and finding a photograph in it.<\/p>\n\nSee the Pen [Image gift box (hover to reveal)](https:\/\/codepen.io\/smashingmag\/pen\/LYaPPPo) by Temani Afif<\/a>.<\/p>See the Pen Image gift box (hover to reveal)<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nPretty neat, right? You might think this is an easy thing to pull off. All we really need is an overlay above the image that we translate, and, boom, we\u2019re done, right?<\/p>\n
That\u2019s true. But if you check the code, you won\u2019t find any additional elements in the markup other than the exact same
<\/code> tag we used last time. Plus, we cannot even use pseudo-elements to make this work. This is what makes such an effect a bit more challenging.<\/p>\nDon\u2019t look at the code right now. Let\u2019s build it together by breaking the demo into isolated little CSS tricks.<\/p>\n
The Image And Sliding Overlay<\/h2>\n
You would be correct in thinking it\u2019s impossible to add an overlay to an image without an extra element. Instead, we are going to fake it and create the illusion of an overlay.<\/p>\n
Let\u2019s start with the following code:<\/p>\n
img {\n --s: 200px; \/* the image size *\/\n \n width: var(--s);\n box-sizing: border-box;\n padding-right: var(--s);\n background: #8A9B0F;\n transition: 1s;\n}\nimg:hover {\n padding: 0;\n}\n<\/code><\/pre>\nWe have defined the width<\/code> as a CSS variable (--s<\/code>) and repurposed it to apply padding along the right side of the element. Combined with box-sizing: border-box<\/code>, this will make the size of the content box equal to 0<\/code>. In other words, we don\u2019t see the image, but we see the background color since it covers the padding area.<\/p>\nOn hover, let\u2019s make the padding equal to 0<\/code>:<\/p>\n\nSee the Pen [Padding animation to reveal the image](https:\/\/codepen.io\/smashingmag\/pen\/jOJrqXo) by Temani Afif<\/a>.<\/p>See the Pen Padding animation to reveal the image<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nNothing surprising, right? By decreasing<\/em> the padding, we increase<\/em> the size of the content box and it slowly reveals the image. We\u2019re basically squishing it vertically and allowing to widen back into place on hover.<\/p>\nLet\u2019s add two more properties to the mix:<\/p>\n
img {\n object-fit: cover;\n object-position: left;\n}\n<\/code><\/pre>\n\nSee the Pen [Adding object-* properties](https:\/\/codepen.io\/smashingmag\/pen\/oNVLLdM) by Temani Afif<\/a>.<\/p>See the Pen Adding object-* properties<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nTada!<\/em> The effect looks much better now, and we have an overlay reveal animation even if, in reality, the overlay you see is the background, which is behind the image! The illusion is perfect.<\/p>\nWhy does it behave like that? The logic is explained nicely over at MDN<\/a>:<\/p>\n\u201cThe replaced content is sized to maintain its aspect ratio while filling the element\u2019s entire content box. If the object\u2019s aspect ratio does not match the aspect ratio of its box, then the object will be clipped to fit.\u201d<\/p><\/blockquote>\n
In other words, the image will maintain its ratio while filling the content box. As a result, the image does not get distorted by the padding as we saw in the first demo \u2014 instead, it is clipped. Then, object-position: left<\/code> aligns the position of the image to the left so it doesn\u2019t move while the size of the content box increases as a result of the decreased padding on hover.<\/p>\nIf we change the position to right<\/code>, you get a different effect:<\/p>\n\nSee the Pen [Using object-position: right](https:\/\/codepen.io\/smashingmag\/pen\/xxBOOJW) by Temani Afif<\/a>.<\/p>See the Pen Using object-position: right<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nInstead of an overlay animation, we have a kind of sliding effect where the image enters from the left. This is directly related to another cool CSS trick that I used in a previous article<\/a> to create a \u201cpop-out\u201d hover effect:<\/p>\n\nSee the Pen [Fancy Pop Out Reveal hover effect!](https:\/\/codepen.io\/smashingmag\/pen\/VwqWRyj) by Temani Afif<\/a>.<\/p>See the Pen Fancy Pop Out Reveal hover effect!<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nFor this article, we are going to rely on the first effect, where the image remains fixed. Here is a demo with all the sliding variations:<\/p>\n\nSee the Pen [A reveal hover effect with one element](https:\/\/codepen.io\/smashingmag\/pen\/GRYEZrr) by Temani Afif<\/a>.<\/p>See the Pen A reveal hover effect with one element<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nYou will notice that it\u2019s pretty easy to switch between the different variations by toggling a couple of values in the CSS.<\/p>\n
Sliding The Overlay Outside The Image<\/h2>\n
Now that we have our overlay, let\u2019s try to slide it outside of the image. Instead of decreasing its size like we did previously, we want it to maintain its size and move it.<\/p>\n
For this, let\u2019s use a box-shadow<\/code> animation:<\/p>\nimg {\n --s: 200px; \/* the image size *\/\n\n box-shadow: 0 0 #8A9B0F; \n}\nimg:hover {\n box-shadow: var(--s) 0 #8A9B0F;\n}\n<\/code><\/pre>\n\nSee the Pen [Adding box-shadow animation](https:\/\/codepen.io\/smashingmag\/pen\/KKEMvNq) by Temani Afif<\/a>.<\/p>See the Pen Adding box-shadow animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nCool, right? We have an overlay above our image that slides over to reveal the image \u2014 without using any extra elements in the markup or pseudo-elements in the styles!<\/p>\n
We can do the same effect using a clip-path<\/code> animation as well.<\/p>\nimg {\n --s: 200px; \/* the image size *\/\n\n box-shadow: 0 0 0 999px #8A9B0F; \n clip-path: inset(0 0 0 0);\n}\nimg:hover {\n clip-path: inset(0 -100% 0 0);\n}\n<\/code><\/pre>\nWe define a box-shadow<\/code> as having a widespread radius, but we won\u2019t actually see it because it\u2019s clipped. On hover, though, we update the inset()<\/code> value to reveal the box-shadow<\/code> on the right side of the image.<\/p>\n\nSee the Pen [Using clip-path instead of box-shadow](https:\/\/codepen.io\/smashingmag\/pen\/YzgWxxK) by Temani Afif<\/a>.<\/p>See the Pen Using clip-path instead of box-shadow<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nUsing the same technique, we can slide the overlay in whatever direction we want. Can you figure out how? Give it a shot by forking the Pen above and changing directions as an exercise before we move to the next part of our work.<\/p>\n
Adding Borders<\/h2>\n
Borders can help create space around the image and get it close to a square box shape. Don\u2019t forget that we want to create a 3D box in the end. But let\u2019s see what happens when we add borders.<\/p>\n\nSee the Pen [Adding border](https:\/\/codepen.io\/smashingmag\/pen\/YzgWrvQ) by Temani Afif<\/a>.<\/p>See the Pen Adding border<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nHmm, not good. The border sits above the overlay, and the image isn\u2019t a perfect square, at least initially. Even if that seems glitchy at first, it\u2019s a logical outcome since the border is painted above the background, and its thickness adds up to the element\u2019s total size.<\/p>\n
What we need to do is adjust the padding to account for the border\u2019s size. Then, let\u2019s make the border transparent so that we can see the background color behind it.<\/p>\n
img {\n --s: 200px; \/* the image size *\/\n --b: 10px; \/* border width *\/\n --c: #8A9B0F;\n \n width: var(--s);\n aspect-ratio: 1;\n box-sizing: border-box;\n padding-top: calc(var(--s) - 2*var(--b));\n border: var(--b) solid #0000;\n box-shadow: 0 0 0 999px var(--c); \n background: var(--c);\n clip-path: inset(0);\n object-fit: cover;\n object-position: bottom;\n}\nimg:hover {\n padding: 0;\n clip-path: inset(-100% 0 0);\n}\n<\/code><\/pre>\n\nSee the Pen [Fixing the border issue](https:\/\/codepen.io\/smashingmag\/pen\/mdoEBQX) by Temani Afif<\/a>.<\/p>See the Pen Fixing the border issue<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nThis looks a lot better. It would be even better if we were to use a different color for the border area. Let\u2019s consider using multiple<\/em> backgrounds.<\/p>\nimg {\n --c: #8A9B0F;\n --_c: color-mix(in srgb, var(--c), #fff 25%);\n \n background:\n linear-gradient(var(--_c) 0 0) no-repeat\n 0 0 \/ 100% 100%,\n var(--c);\n background-origin: border-box;\n box-shadow: 0 0 0 999px var(--_c);\n \/* same as previous *\/\n}\nimg:hover {\n background-size: 100% 0%;\n \/* same as previous *\/\n}\n<\/code><\/pre>\nFirst off, note that we\u2019ve added the color-mix()<\/code> function that allows us to define a new color variation from the original color value (--c:<\/code> #8A9B0F<\/code>) by mixing it with white to get a brighter shade. Then, we use that new color to create a gradient above the element\u2019s background color, which is declared right after the gradient. The same color is also used for the box-shadow<\/code>.<\/p>\nThe idea is to decrease the size of the gradient the same way we do with the padding so that the background-color<\/code> behind the gradient is revealed.<\/p>\n\nSee the Pen [Adding gradient animation](https:\/\/codepen.io\/smashingmag\/pen\/rNRLJpB) by Temani Afif<\/a>.<\/p>See the Pen Adding gradient animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nThat\u2019s really nice! But did you catch the subtle visual issue? If you look closely, you can notice that the overlay is slightly out of alignment with the border.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nThis is because the padding has a transition that goes from s - 2*b<\/code> to 0<\/code>. Meanwhile, the background transitions from 100%<\/code> (equivalent to --s<\/code>) to 0<\/code>. There\u2019s a difference equal to 2*b<\/code>. The background covers the entire area, while the padding covers less of it. We need to account for this.<\/p>\nIdeally, the padding transition would take less time to complete and have a small delay at the beginning to sync things up, but finding the correct timing won\u2019t be an easy task. Instead, let\u2019s increase the padding transition\u2019s range to make it equal to the background.<\/p>\n
img {\n --h: calc(var(--s) - var(--b));\n padding-top: min(var(--h), var(--s) - 2*var(--b));\n transition: --h 1s linear;\n}\nimg:hover {\n --h: calc(-1 * var(--b));\n}\n<\/code><\/pre>\nThe new variable, --h<\/code>, transitions from s - b<\/code> to -b<\/code> on hover, so we have the needed range since the difference is equal to --s<\/code>, making it equal to the background<\/code> and clip-path<\/code> transitions.<\/p>\nThe trick is the min()<\/code> function. When --h<\/code> transitions from s - b<\/code> to s - 2*b<\/code>, the padding is equal to s - 2*b<\/code>. No padding changes during that brief transition. Then, when --h<\/code> reaches 0<\/code> and transitions from 0<\/code> to -b<\/code>, the padding remains equal to 0<\/code> since, by default, it cannot be a negative value.<\/p>\nIt would be more intuitive to use clamp()<\/code> instead:<\/p>\npadding-top: clamp(0px, var(--h), var(--s) - 2*var(--b));\n<\/code><\/pre>\nThat said, we don\u2019t need to specify the lower parameter since padding cannot be negative and will, by default, be clamped to 0<\/code> if you give it a negative value.<\/p>\nWe are getting much closer to the final result!<\/p>\n\nSee the Pen [Fixing the padding issue](https:\/\/codepen.io\/smashingmag\/pen\/ExMgZbW) by Temani Afif<\/a>.<\/p>See the Pen Fixing the padding issue<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nWorth noting that we need to use @property<\/code> to be able to apply a transition to the --h<\/code> variable. The transition won\u2019t work in Firefox at the time of this writing.<\/p>\nThe 3D Effect<\/h2>\n
The last step is to add a touch of 3D to the effect. To better understand how we\u2019re going to approach this, let\u2019s temporarily remove the box-shadow<\/code>, clip-path<\/code>, and the linear-gradient()<\/code> with the image in its revealed state.<\/p>\n\nSee the Pen [The revealed image with border](https:\/\/codepen.io\/smashingmag\/pen\/QWoKpyE) by Temani Afif<\/a>.<\/p>See the Pen The revealed image with border<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nWe\u2019ll take three steps to create the 3D effect I have mapped out in the following figure.<\/p>\n<\/p>\n <\/p>\n
<\/p>\n
<\/a>\n (Large preview<\/a>)
\n <\/figcaption><\/figure>\nFirst, we increase the border\u2019s thickness on the left and bottom sides of the image:<\/p>\n
\nimg {\n --b: 10px; \/* the image border *\/\n --d: 30px; \/* the depth *\/\n\n border: solid #0000;\n border-width: var(--b) var(--b) calc(var(--b) + var(--d)) calc(var(--b) + var(--d));\n}\n<\/code><\/pre>\n<\/div>\nSecond, we add a conic-gradient()<\/code> on the background to create darker colors around the box:<\/p>\n\nbackground: \n conic-gradient(at left var(--d) bottom var(--d),\n #0000 25%,#0008 0 62.5%,#0004 0) \n var(--c);\n<\/code><\/pre>\n<\/div>\nNotice the semi-transparent black color values (e.g., #0008<\/code> and #0004<\/code>). The slight bit of transparency blends with the colors behind it to create the illusion of a dark variation of the main color since the gradient is placed above the background color.<\/p>\nAnd lastly, we apply a clip-path<\/code> to cut out the corners that establish the 3D box.<\/p>\n\nclip-path: polygon(var(--d) 0, 100% 0, 100% calc(100% - var(--d)), calc(100% - var(--d)) 100%, 0 100%, 0 var(--d));\n<\/code><\/pre>\n<\/div>\n\nSee the Pen [The image within a 3D box](https:\/\/codepen.io\/smashingmag\/pen\/JjzRWXZ) by Temani Afif<\/a>.<\/p>See the Pen The image within a 3D box<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nNow that we see and understand how the 3D effect is built let\u2019s put back the things we removed earlier, starting with the padding:<\/p>\n\nSee the Pen [Putting back the padding animation](https:\/\/codepen.io\/smashingmag\/pen\/ExMgWXR) by Temani Afif<\/a>.<\/p>See the Pen Putting back the padding animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nIt works fine. But note how we\u2019ve introduced the depth (--d<\/code>) to the formula. That\u2019s because the bottom border is no longer equal to b<\/code> but b + d<\/code>.<\/p>\n\n--h: calc(var(--s) - var(--b) - var(--d));\npadding-top: min(var(--h),var(--s) - 2*var(--b) - var(--d));\n<\/code><\/pre>\n<\/div>\nLet\u2019s do the same thing with the linear gradient. We need to decrease its size so it covers the same area as it did before we introduced the depth so that it doesn\u2019t overlap with the conic gradient:<\/p>\n\nSee the Pen [Putting back the gradient animation](https:\/\/codepen.io\/smashingmag\/pen\/VwRKpzN) by Temani Afif<\/a>.<\/p>See the Pen Putting back the gradient animation<\/a> by Temani Afif<\/a>.<\/figcaption><\/figure>\nWe are getting closer! The last piece we need to add back in from earlier is the clip-path<\/code> transition that is combined with the box-shadow<\/code>. We cannot reuse the same code we used before since we changed the clip-path<\/code> value to create the 3D box shape. But we can still transition it to get the sliding result we want.<\/p>\nThe idea is to have two points at the top that move up and down to reveal and hide the box-shadow<\/code> while the other points remain fixed. Here is a small video to illustrate the movement of the points.<\/p>\n\n