DEV Community

loading...
Cover image for Drawing an interactive window with a single div and CSS (no JS)

Drawing an interactive window with a single div and CSS (no JS)

Alvaro Montoro
Full-Stack Software Engineer, Mobile Developer, Web technologies enthusiast. CSS aficionado. Active moderator in Stack Overflow en Español. Husband, Father, Longhorn 🀘
Originally published at alvaromontoro.com ・3 min read

First of all, let's clarify one point: when talking about a window, we refer to an actual window like on the wall, not a window on a computer. This post will be about how to draw a window with a single <div> and how to make it interactive, so you can open/close the blinds by pulling the string.

This is what we are going to develop (it looks better on larger screens, as it is sometimes tough to pull the string on mobile):

This demo was inspired by @jh3y 's 3D window demo that uses the new @container query, and that you can see on CodePen too:

I decided to do something simpler in 2D (I should go back to doing 3D CSS demos, it is fun.) And as a challenge, limit the number of elements to the lowest possible. In this case, that was one: a single div. I could draw the window and wall with html and body, but it was not possible to animate them (or at least I couldn't.) Initially, I was going to use the @container query too but then thought of a way of doing it without needing that new feature.

The background/wall is the html/body element (only one of them is needed) after applying multiple gradients as background-image:

Screenshot of the background mimicking a 50s style house interior with wallpaper

With 8 gradients, we can achieve that background:

  • One horizontal linear-gradient for the pink and cream vertical lines.
  • One vertical linear-gradient for the railing and bottom of the wall.
  • One vertical linear-gradient for the decorative top paper (just the white and blue lines)
  • Five radial-gradient to make the circular pattern in the decorative paper on top.

There are two vertical linear-gradient. Why not combine them into a single one? Great question! There are some bugs in Chrome and Firefox that if a linear-gradient has too many stops (8 or more), the lines will get blurred or distorted. For that reason, I had to break one large linear gradient into two smaller ones with less than 8 stops each.

The window is a single <div> element, for which we use different box-shadow and linear-gradient to paint the frame, glass, and reflections.

With the ::before pseudo-element, we add the blinds using a repeating-linear-gradient. And with the ::after, we draw the strings for the blinds.

So far, the window is static. Just a drawing without interaction, but if we add the following CSS:

div {
  /* ... */
  resize: vertical;
  overflow: auto;
  min-height: 300px; /* same as height */
  max-height: 450px;
}
Enter fullscreen mode Exit fullscreen mode

Now the window can grow and shrink vertically within the specified values (the bottom right corner of the div will be the resizing handle in most browsers.) As we used absolute values for the linear gradients and shadows, the window will give the impression that it is always the same size, but the HTML element is actually changing height.

And this is where the ::after pseudo-element comes into play. It will be the only one with relative size, so its background (used for the blinds cord) will grow along with the container.

Finally, the blinds need to go up/down depending on the size of the container... which sounds like a great opportunity for the @container query, but instead, we can calculate the height based on the height of the window (300px) and the height of the container (the div itself).

Using calc() and clamp(), we can determine which size the blinds should have based on the size of the parent:

div::before {
  /* ... */
  /* the background width is fixed, the height changes */
  background-size: 196px clamp(30px, calc(900px - 200%), 290px);
}
Enter fullscreen mode Exit fullscreen mode

And that is how we get an interactive window with a single div element:

Thanks for reading. Let me know if you have any questions or if anything needs further explanation.

Discussion (7)

Collapse
afif profile image
Temani Afif • Edited

You can do it with only body and html but it seems you faced the quirk related to the overflow propagation that didn't allow you to resize the body (one of the hidden quirk of CSS that no one talk about or at least a few are aware of).
Here is a a demo: codepen.io/t_afif/pen/vYxOwOm. The fix is to set an explicit value of overflow to html to avoid the propagation

More details here: drafts.csswg.org/css-overflow-3/#o...

Collapse
alvaromontoro profile image
Alvaro Montoro Author

That's a nice tip

Collapse
inhuofficial profile image
InHuOfficial

Really interesting and fun, I love trying to unpack stuff like this.

As for being hard on a mobile - I am that stupid that I spent at least 30 seconds trying to do it on a desktop - my brain was getting me to drag the handle up instead of pull it down 🀣🀣

Very impressive, well deserving of a ❀ and πŸ¦„!

Collapse
alvaromontoro profile image
Alvaro Montoro Author

Thanks! 😊

Collapse
afif profile image
Temani Afif

PS: you can omit display:block when using position:absolute (the element will get blockified by default)

Collapse
alvaromontoro profile image
Alvaro Montoro Author

Old habits die hard 😳

Collapse
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

CSS never ceases to amaze me! Very cool demo.

Gonna have to try to unpack that code later to understand how it works.

Forem Open with the Forem app