slide_icon

 

WELCOME TO   MY BLOG

You can see my thought and can share your thought here.

Ekta Bhargava
The State of Responsive 3D Shapes

The State of Responsive 3D Shapes

As some people might know, I've always loved 3D geometry. Which has meant getting drawn towards playing with CSS 3D transforms in order to create various geometric shapes. I've built a huge collection of such demos, which you can check out on CodePen. Because of this, I've often been asked whether it would be possible to create responsive 3D shapes using, for example, % values instead of the em values my demos normally use. The answer is a bit more complex than a yes/no answer, so I thought it would be a good idea to put it in an article. Let's dive in! Responsive 3D shapes using % values This is possible, right? It is, but it complicates things. To illustrate that, let's take an example. Let's say we want to create a cube. Before we actually start working on that, two things. If you need a refresher of how creating a simple, non-responsive rectangular cuboid with the use of CSS 3D transforms works, you can check out this older article all the way to the bar distribution part. It explains (in extreme detail even) a number of concepts related to how CSS transforms work, the process of building the cuboid with clean and compact code and I'm not going to go through all these things again here. Let's recap what % values mean for a few CSS properties we might want to set on an element. % values for various CSS properties For width, padding and margin a % value is relative to the width of the parent of the element on which we are setting these properties. Let's consider the following situation: we have an element, a child of the body. On this element, we set % value width, padding and margin. .boo { width: 50%; padding: 10%; margin: 5%; } If we resize the viewport such that the width of the body (the parent of our element) is 300px, then our element has a width of 150px (50% of 300px), a padding of 30px (10% of 300px) and a margin of 15px (5% of 300px), as it can be seen when inspecting the element. Inspecting the element (see live demo). For this to work as we've intended, the width of the parent shouldn't depend on that of its children. This is a circularity we should avoid. It happens, for example, if the parent has position: absolute. In this case, it appears that the width of the parent is computed such that the content of our element fits, then our element's width, padding and margin get computed as % values of the parent's width. For height, a % value is relative to the height of the parent of the element on which we are setting the height property. Let's consider our element is the child of the body, which we've made to cover the entire viewport in height. body { height: 100vh; } .boo { height: 50%; } If we resize the viewport such that the height of the body (the parent of our element) is 300px, then the height of our element is 150px (50% of 300px), as it can be seen when inspecting the element. Inspecting the element (see live demo). For this to work as we've intended, the height of the parent shouldn't depend on its children. This is another circularity we should avoid. But unlike in the case of width, we find ourselves in this situation whenever we don't explicitly set a height on the parent. For transform, if a translate() / translateX() / translateY() / translateZ() / translate3d() function uses a % value, then this value is relative to the size of the element along that axis. Note that these are the axes of the element's local system of coordinates, which gets transformed in 3D along with the element itself. Its x axis always points to the right of the element, its y axis always points to the bottom of the element and its z axis always points out from the front of the element. A value of translate(50%) or translate(50%, 0) or translateX(50%) or translate3d(50%, 0, 0) moves the element along the x axis by half its own width. This means that the vertical midline of the element ends up being where its right edge was initially. In this live test, the grey box represents the element in its intial position, with no transform applied. The orange box represents the element with a transform of translateX(50%) applied. Applying a transform of translateX(50%) on an element. A value of translate(0, 50%) or translateY(50%) or translate3d(0, 50%, 0) moves the element along the y axis by half its own height. This means that the horizontal midline of the element ends up being where its bottom edge was initially. In this live test, the grey box represents the element in its intial position, with no transform applied. The orange box represents the element with a transform of translateY(50%) applied. Applying a transform of translateY(50%) on an element. What about a value of translateZ(50%) or translate3d(0, 0, 50%)? Well, no anomaly here, it moves the element along the z axis by half its size along that axis. But what's the size of the element along the z axis? We're not setting it anywhere, that's for sure, but all elements are flat, contained in a plane, and consequently, their size along their z axis is always 0. This means that a translation along the z axis that uses a % value does nothing, because any % of 0 is still 0. If we check the computed value of translateZ(50%), we can see that it's none. Consequences of the way % values work The first one is that using % values we cannot create elements whose width depends on the viewport height and creating elements whose height depends on the viewport width isn't that straightforward. We wanted to create a cube, so let's see how we can create a square whose edge length is 20% of the viewport width. First of all, we make sure our square element has both width and height equal to 0. We can set these explicitly, or we can absolutely position our square, which is what we choose to do in this case as it comes in handy later when creating the cube. Then we set the padding on this square element to half the % value we want for the cube's edge length 20%/2 = 10% - this makes every padding value (padding-top, padding-right, padding-bottom and padding-left) be 10% of the viewport width. $edge-len: 20%; .square { position: absolute; padding: .5*$edge-len; } Adding up a padding-left of 10% and a padding-right of 10% gives us 20% of the viewport width horizontally across the square. Adding up a padding-top of 10% and a padding-bottom of 10% gives us 20% of the viewport width vertically across the square. The result is a square that scales with the viewport width. We have a square that scales with the viewport width (see live test). This looks great, but we have to keep in mind that we have zeroed our element's width and height, which means we cannot set box-sizing: border-box on it and adding a border that wouldn't add up to the total space occupied by our square on the screen in this case requires either emulation with an inset box-shadow or subtracting the border-width from the padding using calc(). $edge-len: 20%; $bw: .5em; // border width .square { position: absolute; border: solid $bw currentColor; padding: calc(#{.5*$edge-len} - #{$bw}); } Also getting cool effects using background-clip, like a transparent space between the background and border becomes more complicated as well. For a simple spaced out border we need to subtract both the border-width and the box-shadow spread from the padding using calc(). Not to mention we also need a margin equal to the box-shadow spread to fix positioning. $edge-len: 20%; $bw: .5em; // border width $s: .75em; // shadow spread .square { position: absolute; margin: $s; border: solid $bw transparent; padding: calc(#{.5*$edge-len} - #{$bw + $s}); box-shadow: 0 0 0 $s currentColor; background: #e18728 padding-box; } For a double spaced out border, we have no choice but to use a pseudo-element. $edge-len: 20%; $bo: .25em; // outer border width $so: .5em; // outer gap width $bi: 1em; // inner border width $si: .75em; // inner gap width $inner-len: calc(100% - #{2*($so + $si + $bo + $bi)}); .square { position: absolute; padding: .5*$edge-len; box-shadow: inset 0 0 0 $bo; &:before { position: absolute; border: solid $bi currentColor; padding: $si; width: $inner-len; height: $inner-len; transform: translate(-50%, - 50%); background: #e18728 content-box; content: ''; } } The second consequence is that translating such an element along its z axis (forward and backward) by an amount that depends on at least one of its viewport-dependant dimensions (width and height) isn't that straightforward either. We have already established that we cannot use a % value to translate an element along its z axis by an amount that depends on a viewport dimension. However, with transforms, we have more than one way of bringing an element into a certain position. For example, rotate(53deg) translate(5em) is equivalent to translate(3em, 4em) rotate(53deg), as it can be seen in this live test. So, while we cannot move our square forward by half of its width using a translateZ() with a % value, we can come up with a transform chain that doesn't use a translateZ() function, but still puts our element right where we want it. Let's consider the square with no border we got above and let's say we want to move it forward by half its edge length. We have more than one way of doing this. For example, we could start by rotating it around its y axis by -90°. This rotates both our square and its system of coordinates such that it now faces left and its x axis points towards us, out of the screen. Next, we translate it along its x axis by 50%. The x axis now points towards us, so this means our square gets moved forward by half its edge length. The vertical midline of our square is in the position we want it, but we're seeing our square from the right and we want to see it from the front. This is why our final step is to reverse the initial rotation. So we rotate our square by 90° around its y axis - this makes it face us again, while its x axis points to the right again. These three steps result in the rotateY(-90deg) translateX(50%) rotateY(90deg) transform chain and they are illustrated by the following demo (click to play): See the Pen translating a square forward by half its edge length without translateZ - method 1 by Ana Tudor (@thebabydino) on CodePen. Another transform chain that gives the same result is rotateX(90deg) translateY(50%) rotateX(-90deg). Just like the previous one, it uses the trick of rotating the element (and its local system of coordinates along with it) such that we make another axis (y in this case) point in the direction that the z axis was before this rotation, translating along this other axis (y) and then finally reversing the first rotation. We can see this illustrated in the Pen below: See the Pen translating a square forward by half its edge length without translateZ - method 2 by Ana Tudor (@thebabydino) on CodePen. Note that the previous two demos don't work in Edge. Creating a responsive cube using % values We start with the following structure: <div class='cube'> <div class='cube--face'></div> <div class='cube--face'></div> <div class='cube--face'></div> <div class='cube--face'></div> <div class='cube--face'></div> <div class='cube--face'></div> </div> We could simplify this with a preprocessor like Haml or Slim or whatever. .cube - 6.times do .cube__face I do this because I don't like writing the same thing multiple times and, with something like Haml, I'm not even introducing a loop variable that I'm not using in the loop anyway. It's mostly down to personal preference here since we don't have a lot of code anyway. There are just 7 HTML elements: the .cube element and its 6 face children1. We take the body element to be our scene, so we make it cover the entire viewport height and set a perspective on it. Remember that this is an arbitrary decision, taken just so that we simplify things. We could just as well take our scene to be any element whose width depends on the viewport width in some way. We then absolutely position our elements, making sure that the .cube is dead in the middle of the scene and it has perspective-style: preserve-3d so that its children can be transformed in 3D as well. body { height: 100vh; perspective: 20em; } div { position: absolute; } .cube { top: 50%; left: 50%; transform-style: preserve-3d; } Next step would be to size our cube faces like before, using padding, right? Well, if we do that, we discover that the padding is actually evaluated to 0px! Checking out the computed styles in dev tools: our % value padding is evaluated to 0px! That's because they aren't children of the body anymore, they are children of the .cube element, which is an absolutely positioned 0x0 element. We can get past this by sizing the cube element using the padding trick and then making its children the same size. $edge-len: 16%; .cube { /* previous styles */ margin: -.5*$edge-len; padding: .5*$edge-len; &__face { top: 0; right: 0; bottom: 0; left: 0; background: orange; } } We've also added a negative margin on the .cube element, so that we have its midpoint dead in the middle of the scene instead of its top left corner. See the Pen responsive cube using % values - step 1 by Ana Tudor (@thebabydino) on CodePen. This is a start. All the face elements are the size we wanted them to be and they're right where we wanted them. Now let's see how we can transform the 6 face elements in 3D such that they form a cube. We start by dividing the 6 faces into two categories: 4 lateral faces and 2 base faces. We consider the lateral faces to be the right, back, left and front ones and the base ones the top and the bottom. This is an arbitrary decision. We could have taken the lateral ones to be the bottom, front, top and back ones and the base ones to be the left and the right. We take the first 4 face elements in DOM order to be the lateral ones and the others to be the base ones. The next thing we do is pick the axis we want to do the translations along, the axis which we want to point towards where on the cube (front, bottom, right...) we want to place our face elements. It cannot be the z axis because we need to work with % values here, so we pick the x axis. Again, remember that this is completely arbitrary - in fact, you could take using the y axis instead as something to try after you finish reading this. We take the first face element. Without any transforms applied, its x axis points right. So we make it be the face on the right of the cube. We translate it by 50% in the positive direction along its x axis. This makes its vertical midline coincide with the vertical midline of the cube's right face. We then rotate it by 90° around its y axis to place it into the desired position on the cube (facing right). See the Pen position face element on the right of cube by Ana Tudor (@thebabydino) on CodePen. The CSS for this first .cube__face element is: .cube__face:first-child { transform: translateX(50%) rotateY(90deg); } For consistency purposes, we can write this in the following equivalent form (a rotation of 0° around any axis has no effect): .cube__face:nth-child(1) { transform: rotateY(0deg) translateX(50%) rotateY(90deg); } We move on to the second face, which we put on the back of the cube. This means we first rotate it by 90° around its y axis so that its x points towards that face (which means towards the back). Then we translate it by 50% in the positive direction along its x axis. Now its vertical midline coincides with that of the cube's back face. The final step is to rotate it again by 90° around its y axis so that it's in the desired position on the cube (facing back). See the Pen position face element on the back of cube by Ana Tudor (@thebabydino) on CodePen. We write down the transform for this face: .cube__face:nth-child(2) { transform: rotateY(90deg) translateX(50%) rotateY(90deg); } Now it's the turn of the third face to be positioned. This time, on the left of the cube. We start by rotating it by 180° around its y axis so that we can make its x axis point left, where we want to put it. Then we translate it by 50% in the positive direction along this x axis. This puts its vertical midline on that of the cube's left face. Finally, we rotate it by 90° more around its y axis so that it's positioned correctly on the cube (facing right). See the Pen position face element on the left of cube by Ana Tudor (@thebabydino) on CodePen. So the transform chain in this case is: .cube__face:nth-child(3) { transform: rotateY(180deg) translateX(50%) rotateY(90deg); } Now we got to the final lateral face! This one we position on the front of the cube. Just as with the previous three, the first thing we need to do is rotate it around its y axis in such a way that it makes its x axis point forward. We've seen before that this can be achieved by a -90° rotation around the y axis. But a -90° rotation puts an element in the same position as a 270° one, so, for consistency reasons, we're using 270° here. After the 270° rotation around the y axis, we translate our element by 50% in the positive direction along its x axis. And finally, we rotate it by 90° more around its y axis so that it faces forward, not left. See the Pen position face element on the front of cube by Ana Tudor (@thebabydino) on CodePen. The transform chain for this last lateral face is: .cube__face:nth-child(4) { transform: rotateY(270deg) translateX(50%) rotateY(90deg); } Now we can move on to the base faces. To position the first base face on the bottom of the cube, the first step is to rotate it such that its x axis points in that direction (down). That's a 90° rotation around its z axis. The following step is to translate it by 50% in the positive direction along this x axis that now points down, bringing its midline onto the cube's bottom face. And lastly, we rotate it by 90° around its y axis so that it faces down. See the Pen position face element on the bottom of cube by Ana Tudor (@thebabydino) on CodePen. This means that the CSS for positioning this face is: .cube__face(5) { transform: rotateZ(90deg) translateX(50%) rotateY(50%); } And we got to the last face, which we put on top of the cube. To do so, we start by rotating such that its x axis points up - that's a -90° rotation around its z axis. Following this, we translate it by 50% in the positive direction along this x axis that now points up. This way, we've brought the face's midline on the cube's top face. The final step is to rotate it by 90° rotation around its y axis so that it faces up. See the Pen position face element on the top of cube by Ana Tudor (@thebabydino) on CodePen. .cube__face(6) { transform: rotateZ(-90deg) translateX(50%) rotateY(50%); } Putting everything together, we can start to notice some patterns: .cube__face(1) { /* 1 = 0 + 1 */ transform: rotateY(0deg) /* 0° = 0*90° */ translateX(50%) rotateY(50%); } .cube__face(2) { /* 2 = 1 + 1 */ transform: rotateY(90deg) /* 90° = 1*90° */ translateX(50%) rotateY(50%); } .cube__face(3) { /* 3 = 2 + 1 */ transform: rotateY(90deg) /* 180° = 2*90° */ translateX(50%) rotateY(50%); } .cube__face(4) { /* 4 = 3 + 1 */ transform: rotateY(270deg) /* 270° = 3*90° */ translateX(50%) rotateY(50%); } .cube__face(5) { /* 5 = 4 + 1 */ transform: rotateZ(90deg) /* 90° = 1*90° = ((-1)^4)*90° */ translateX(50%) rotateY(50%); } .cube__face(6) { /* 6 = 5 + 1 */ transform: rotateZ(-90deg) /* -90° = -1*90° = ((-1)^5)*90° */ translateX(50%) rotateY(50%); } The first thing to notice here is that the last two transform functions in the chain are always the same. The next thing is that we can derive a general formula for the lateral faces and the base faces. For the lateral faces, the first transform function is rotateY($i*90deg), where $i is the face index. For the base faces, the first transform function is rotate(pow(-1, $i)*90deg). This means that we can compact it all in a loop, like this: @for $i from 0 to 6 { .cube__face:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateZ(pow(-1, $i)*90deg)) translateX(50%) rotateY(90deg); } } The result of adding the above code to what we had before can be seen in the following Pen: See the Pen responsive cube using % values - step 2 by Ana Tudor (@thebabydino) on CodePen. It doesn't look like anything has changed, but, if we make the faces semitransparent, give them a sort of outline and animate the rotation of the .cube element, it becomes obvious that we now have a 3D shape. A responsive one even! Responsive cube created using % values (see live demo). We could also tweak this a bit so that the scene isn't the body anymore: See the Pen responsive cube using % values - step 4 (scene != body) by Ana Tudor (@thebabydino) on CodePen. Now this doesn't seem too bad. For the non-responsive case, the transform chain needed to position a face on the cube has a length of 2 - a rotation and a translateZ(): @for $i from 0 to 6 { .cube__face:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg)) translateZ(.5*$edge-len); } } With a responsive cube created using % values, it has a length of 3 - a rotation and then a translateX(50%) and a rotateY(50%). That's just 6 more transform functions in total - we can live with that. The problem is that the more we want to do, the more complex things get. A more complex example Let's say we want to have something like a Rubik's cube. This is an assembly of cubes, with 3 along each dimension. That's 3*3*3 = 27 cubes in total. This means we have the following structure: .assembly - 27.times do .cube - 6.times do .cube__face Each cube gets created almost as above, with a few differences. The most important one is that now the cubes' parent isn't the scene anymore, it's the .assembly element. Which is absolutely positioned like everything else in the scene, so by default, its size is 0x0. So in this case, we need to size the .assembly relative to the scene width, then make the .cube elements and their faces the same size as the assembly. $edge-len: 8%; div { position: absolute; transform-style: preserve-3d; } .assembly { top: 50%; left: 50%; margin: -.5*$edge-len; padding: .5*$edge-len; transform: rotateX(-30deg) rotateY(30deg); } [class*='cube'] { top: 0; right: 0; bottom: 0; left: 0; } What we have so far is 27 cubes, all of them positioned in the middle of the scene: See the Pen responsive cube assembly using % values - step 0 by Ana Tudor (@thebabydino) on CodePen. To make something like a Rubik's cube, we need to distribute these cubes along the three dimensions of space. Along each dimension, we have 3 cubes. One gets translated by its edge length in the negative direction of the axis, the second one stays in place and the third one is translated by its edge length in the positive direction of the axis. This means that the values for these translations are -100%, 0 and 100%. These three values can be written as -1*100%, 0*100% and 1*100% respectively. Since our indices along each axis are 0, 1 and 2, this means that the translation along each axis is the index minus 1, all multiplied by 100%. So our basic distribution code looks like this: @for $i from 0 to 3 { // along the x axis $x: ($i - 1)*100%; @for $j from 0 to 3 { // along the y axis $y: ($j - 1)*100%; @for $k from 0 to 3 { // along the z axis $z: ($k - 1)*100%; $idx: $i*3*3 + $j*3 + $k + 1; .cube:nth-child(#{$idx}) { transform: translate3d($x, $y, $z); } } } } The problem is that this code does nothing when using % values. If we get rid of the $z and use just a translate($x, $y), then we can see the cubes distributed along the first two dimensions. This distribution along the x and y axes looks perfect in Chrome and Edge. Firefox has 3D order issues, but we can easily fix these with z-index in the static case. Distribution along the x and y axes, expected result and what we get in Chrome and Edge (left) vs. Firefox result (right) With the way we've rotated our assembly, the x axis points back and we want the cubes in the back to be behind those in the front. So the higher the $i value, the lower the z-index should be - this means we add it with minus when we compute the value. The y axis points down and we want the cubes on higher up to be above those below. So the higher the $j value, the lower the z-index should be - this means we also add it with minus. The z axis points right and we want the cubes on the left to be behind those on their right. This means that the higher the $k value, the higher the z-index, so we need to add it with plus. We get that: z-index: $k - $i - $j; Now it also looks fine in Firefox: See the Pen responsive cube assembly using % values - step 3 by Ana Tudor (@thebabydino) on CodePen. But what about the third dimension? Well, we can do as before: rotate every cube around its y axis such that its x axis points in the direction its z axis originally pointed and then translate by $z along this x axis. This means our transform chain becomes: transform: translate($x, $y) rotateY(90deg) translateX($z); This does the trick: Responsive assembly of cubes (see live demo). We got our nice, responsive, Rubik's cube structure. But it's at the expense of 2 extra transform functions per cube. And we have 27 cubes, so that's 54 extra transform functions. Our CSS just got a lot bigger. Responsive 3D shapes using viewport units This should be easier, right? Yes, but... depending on what units we choose and on how we might want to animate our 3D shapes, we could run into bugs. I like using viewport units better than using % because it doesn't come with the same limitations and complications. I can size the shapes depending on the viewport height, not just on the width. Even better, depending on the smaller viewport dimension! And creating the shapes works just the same as when using px or em, no need to add extra transform functions to the chain. However, we need to be aware of a few browser issues. Edge doesn't yet support vmax This hasn't bothered me a lot because I've rarely wanted vmax-sized shapes and, on the very rare occasions that I have, I was able to get around that one way or another. Current solutions The first workaround that comes to mind when wanting to create a square whose size depends on the maximum viewport dimension is to set its width and height to a value with vw units and its min-width and min-height to the same value with vh units. .boo { width: 20vw; height: 20vw; min-width: 20vh; min-height: 20vh; } It works like a charm: Testing vmax emulation (see live demo). We can make this more maintainable by using Sass: $edge-len-landscape: 20vw; $edge-len-portrait: $edge-len-landscape*1vh/1vw; .boo { width: $edge-len-landscape; height: $edge-len-landscape; min-width: $edge-len-portrait; min-height: $edge-len-portrait; } Now if we want to change how much the edge length depends on the maximum dimension, we only need to change the value of $edge-len-landscape and we don't have to worry about maybe forgetting to change one value somewhere. Unfortunately, this is not enough when we want to play in 3D. To position the faces in the middle, we'll want a negative margin that depends on the edge length. To create a cube, we need to translate each face by an amount that depends on the edge length. But which one? The portrait or the landscape one? We have two options that work today: we can either use an orientation (or an aspect ratio) media query or we could use % values inside the translate() function like we did before. So let's go back to our cube example. With the first method, we combine the above emulation with the regular transform chains we'd apply in the non-responsive case and we add a media query for the transform part: $edge-len-landscape: 20vw; $edge-len-portrait: $edge-len-landscape*1vh/1vw; .cube__face { margin: -.5*$edge-len-landscape; width: $edge-len-landscape; height: $edge-len-landscape; min-width: $edge-len-portrait; min-height: $edge-len-portrait; @for $i from 0 to 6 { &:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg)) translateZ(.5*$edge-len-landscape); } } @media (orientation: portrait) { margin: -.5*$edge-len-portrait; @for $i from 0 to 6 { &:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg)) translateZ(.5*$edge-len-portrait); } } } } This looks a like a bit too much, so it's probably better if we use a mixin: $edge-len: 20vw; @mixin faces($l: $edge-len) { margin: -.5*$l; width: $l; height: $l; @for $i from 0 to 6 { &:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg)) translateZ(.5*$l); } } } .cube__face { @include faces(); @media (orientation: portrait) { @include faces($edge-len*1vh/1vw); } } The result of the above code is a responsive cube depending on the maximum viewport dimension. Responsive cube depending on maximum viewport dimension without using vmax (see live demo). With the second method, we use %-valued translations in combination with the vmax-emulating sizing. In addition to the transform chains we had when doing everything with percentages, we also need to add a translate(-50%, -50%) at the beginning to compensate for the edge-dependant negative margin so that our square starts as being positioned in the middle of the scene. If that's not too clear, consider this: we position all elements absolutely, we put the .cube in the middle of the scene (top: 50%; left: 50%;) and then we size its faces (using the vmax emulation method). But this makes the corner of our faces be in the middle of the scene, which is not what we wanted. See the Pen absolutely position & relative size #0 by Ana Tudor (@thebabydino) on CodePen. Even if we don't know what negative margin to apply because we don't know whether we're in portrait or in landscape mode, we can still fix this with a translate(-50%, -50%) - this translates the element to the left by half its width (whatever that may be, we don't need to know it) and up by half its height. This is how we get our cube faces to start from the right position. See the Pen absolutely position & relative size #1 by Ana Tudor (@thebabydino) on CodePen. Now we have to distribute the faces on the .cube, but if we simply add the transform chains from the all % case, then they overwrite transform: translate(-50%, -50%) and the .cube isn't positioned properly anymore. See the Pen cube sized depending on max viewport dimension (no vmax!) - positioning fail by Ana Tudor (@thebabydino) on CodePen. This is why we need to chain translate(-50%, -50%) before the other transform functions for each face: @for $i from 0 to 6 { .cube__face:nth-child(#{$i + 1}) { transform: translate(-50%, -50%) if($i < 4, rotateY($i*90deg), rotateZ(pow(-1, $i)*90deg)) translateX(50%) rotateY(50deg); } } Doing this gives us the result we were after. Responsive cube depending on maximum viewport dimension without using vmax (see live demo). Future solutions At this point, CSS variables are listed as "in development" for Edge so, once they're supported, we should be able to start using them for a much cleaner workaround than the two above. The basic idea would be to use a CSS variable for the edge length. We initially set its value to be a vw one, then changing this value to vh inside a media query does the trick. .boo { --edge-len: 20vw; width: var(--edge-len); height: var(--edge-len); } @media (orientation: portrait) { .boo { --edge-len: 20vh; } } For creating 3D shapes things get a bit trickier because some properties require values that are not equal to the edge length, but computed from it. For example, for our cube, we need to set on the faces a margin that's equal to half the edge length and them we also need to translate the faces by half the edge length. Solution? calc() to the rescue! .cube__face { --edge-len: 20vw; margin: calc(-.5*var(--edge-len)); width: var(--edge-len); height: var(--edge-len); @for $i from 0 to 6 { &:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg)) translateZ(calc(.5*var(--edge-len))); } } } @media (orientation: portrait) { .cube__face { --edge-len: 20vh; } } See the Pen cube sized depending on max viewport dimension with CSS variables by Ana Tudor (@thebabydino) on CodePen. This works perfectly in Chrome and Firefox, but will it work in Edge when variables land there too? Well, there's another problem in Edge. calc() works inside translate functions when used for the translate values along the x and y axes, but not for those along the z axis. If we use calc() inside translateZ() or for the translate value along the z axis (the third argument) inside translate3d(), then the computed value for the transform in Edge ends up being none. The first workaround that comes to mind is to start with another CSS variable, --inradius, from which we compute --edge-len: .cube__face { --inradius: 10vw; --edge-len: calc(2*var(--inradius)); margin: calc(-1*var(--inradius)); width: var(--edge-len); height: var(--edge-len); @for $i from 0 to 6 { &:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg)) translateZ(var(--inradius)); } } } @media (orientation: portrait) { .cube__face { --inradius: 10vh; } } The inradius for both a cube and any of its square faces is equal to half the edge length. See the Pen cube sized depending on max viewport dimension with CSS variables #2 by Ana Tudor (@thebabydino) on CodePen. This should probably work in Edge once CSS variables arrive, but we won't know for sure until that actually happens. Using vmin values inside translate functions fails in Edge This sounds really inconvenient because vmin seems to be the go to unit when wanting to create responsive 3D shapes. Fortunately, this has a handy fix: setting the font-size in vmin and then working with em! See the Pen is earth flat? (pure CSS 3D) by Ana Tudor (@thebabydino) on CodePen. Animating translations that use viewport units fails in most browsers after viewport resize This isn't a problem if we're not animating translations using viewport units. However, it's the most problematic issue for me because I often want to animate things beyond a simple rotation of the 3D assembly and I haven't been able to find a fix for this problem. Consider the following example: we have a rectangle we animate to go from the left of the screen to the right (live demo): .boo { margin-left: -5em; width: 10em; height: 7.5em; animation: ani 1s ease-in-out infinite alternate; } @keyframes ani { to { transform: translate(100vw); } } Before resizing the viewport, everything looks fine in all browsers. But if we increase or decrease the viewport width, both Chrome and Safari still animate to the same absolute value as before. It's like the 100vw value got replaced by its px equivalent. The rectangle isn't animated to the right of viewport anymore, but beyond it if we've decreased the viewport width and to some point in the middle if we've increased it. And the font-size trick used for the previous Edge issue doesn't help in this case. Edge does something weird too - after resize, the element is translated to the right of the viewport, then just disappears, then flashes back onto the screen. Firefox is the only browser that gets this one right. For a simple 3D example, let's say we have a 3D assembly with two identical cubes. <div class='assembly'> <div class='cube'> <div class='cube__face'></div> <!-- 5 more cube faces here --> </div> <div class='cube'> <div class='cube__face'></div> <!-- 5 more cube faces here --> </div> </div> Or, the DRY preprocessor version: .assembly - 2.times do .cube - 6.times do .cube__face Their edge length is in vmin units (well, in em units, but with font-size set to a vmin value so that we avoid the Edge bug mentioned before). We put the .assembly dead in the middle of the scene and then we offset the cubes to the right and to the left by a quarter of the viewport width (that's 25vw). So the basic styles before we actually do anything in 3D would look something like: $edge-len: 16em; $offset: 25vw; body { height: 100vh; perspective: 32em; } div { position: absolute; transform-style: preserve-3d; } .assembly { top: 50%; left: 50%; } .cube { &:nth-child(1) { left: -$offset; } &:nth-child(2) { left: $offset; } &__face { margin: -.5*$edge-len; width: $edge-len; height: $edge-len; background: url($image); /* so we can see it */ } } The result so far can be seen in the following Pen: See the Pen colliding cubes - step #0 by Ana Tudor (@thebabydino) on CodePen. We could compact the cube offsets a bit from this point with an index-based sign switching so that we can set them in a loop. -$offset = -1*$offset and $offset = 1*$offset. Also, -1 = (-1)^1 = (-1)^(0 + 1) and 1 = (-1)*(-1) = (-1)^2 = (-1)^(1 + 1). That gives us: .cube { @for $i from 0 to 2 { &:nth-child(#{$i + 1}) { left: pow(-1, $i + 1)*25vw; } } } The faces are positioned exactly like in the non-responsive case. @for $i from 0 to 6 { .cube__face:nth-child(#{$i + 1}) { transform: if($i < 4, rotateY($i*90deg), rotateX(pow(-1, $i)*90deg)) translateZ(.5*$edge-len); } } We now have the two cubes: See the Pen colliding cubes - step #1 by Ana Tudor (@thebabydino) on CodePen. The next step is to animate them so that they collide. This means translating the first one towards the right (in the positive direction of the x axis) and the second one towards the left (the negative direction of the x axis). Translating them by the $offset puts them both right in the middle, overlapping, but that's not what we want. We want the right face of the first cube to touch the left face of the second cube right in the middle. This means that they both need to be half an edge length away from the middle. It's like we've translated them by the $offset in one direction and then by half the edge length in the opposite direction. So the @keyframes look like this: .cube { animation: move 2s ease-in infinite alternate; @for $i from 0 to 2 { &:nth-child(#{$i + 1}) { left: pow(-1, $i + 1)*25vw; animation-name: move#{$i + 1}; } } } @keyframes move1 { to { transform: translateX(calc(1*(#{$offset} - #{.5*$edge-len}))); } } @keyframes move2 { to { transform: translateX(calc(-1*(#{$offset} - #{.5*$edge-len}))); } } We can make this code more efficient by generating the @keyframes within the .cube loop: .cube { animation: move 2s ease-in infinite alternate; @for $i from 0 to 2 { &:nth-child(#{$i + 1}) { left: pow(-1, $i + 1)*25vw; animation-name: move#{$i + 1}; } @at-root { @keyframes move#{$i + 1} { to { transform: translateX(calc(#{pow(-1, $i)}*(#{$offset} - #{.5*$edge-len}))); } } } } } The result looks great in all browsers. That is... until we resize the viewport. WebKit browsers don't update the translation amount in the keyframes to match the new viewport and neither does Edge. Viewport-relative translation amount doesn't get updated after viewport resize in WebKit browsers and Edge (see live demo). A more complex situation where this breaks things is when morphing from one 3D shape to another via truncation. See the Pen tetrahedron truncation sequence (interactive, ~responsive) by Ana Tudor (@thebabydino) on CodePen. Resizing the viewport in this case causes the triangular faces that open up when we start truncating the tetrahedron from its vertices not to be positioned correctly anymore as their place in 3D is also determined by a translation whose value depends on the tetrahedron's edge length (which uses viewport units so that the entire 3D shape scales as the viewport gets resized). 1 I've seen a lot of demos and tutorials adding .front, .back, .left, .right, .top, .bottom classes to these face elements and I personally find that pointless, even damaging. If we rotate the whole cube in 3D, which we often want to do, then the .front face isn't in front from our point of view anymore and that can get confusing. Giving them each a different name is kind of distracting fron the fact that they are all similar entities, it doesn't matter which we put where - the first one by DOM order could be placed on the front of the cube, on the right or on the bottom depending on the generic distribution formula we pick. Using a general index-dependent formula that we can position them all in 3D is another very good reason to ditch these classes. The State of Responsive 3D Shapes is a post from CSS-Tricks

Get Out the Vote, CSS Style!

Get Out the Vote, CSS Style!

The United States general election is tomorrow! New leaders — including a new president — will be elected to office on November 8 after citizens make their voices heard by casting votes for candidates. Where does CSS-Tricks fit into the election? This is far from being a place to discuss politics, though we do admit to the occasional dive into office politics such as helping teams have productive discussions about code and how we define our roles in the workplace. What we do care about when it comes to the election is that everyone who is eligible and registered to vote exercises that right. In fact, Chris summed it nicely in a single sentence over on the CodePen blog: No matter who you vote for, make sure you vote. Regardless of where you sit politically, we are all bound by our love for CSS. It's the reason we gather here on this site or wherever you subscribe to these posts. We're making a call for everyone to proudly display their right to vote in this election. Those who have voted in past elections are aware that doing so earns you a sticker to wear in public that lets others know you cast a vote. I got mine the other day in my absentee ballot packet and decided to make a version of it in CSS: See the Pen I Voted Sticker by Geoff Graham (@geoffgraham) on CodePen. CodePen has been featuring Pens (Colleciton) that encourage folks to get out and vote. That was in preparation for this round up we planned to do here. Let's do this! Paul Sullivan made a Pen based off one of the candidate's social media posts showing where the different presidential candidates land on the issues: See the Pen Get out and vote - Nov. 8th by Paul Sullivan (@pwsm50) on CodePen. The candidates with the biggest spotlights: See the Pen Clinton & Trump - Graphic Design (Responsive) by Richik SC (@richiksc) on CodePen. Chris with a simple message that we're trying to emphasize today: See the Pen Voting is our Voice by Chris Coyier (@chriscoyier) on CodePen. Click around Lewi Hussey's Pen to soak in the patriotism: See the Pen 2016 ELECTION by Lewi Hussey (@Lewitje) on CodePen. Need some icons? See the Pen Vote! #Govicons by Mark Ostrander (@ostranme) on CodePen. Or an incredibly America saturated web font? See the Pen HWT American Chromatic by Tim Brown (@timbrown) on CodePen. Many of us will be glued to our TV's on Tuesday night. Let's hope the picture doesn't start doing this on us: See the Pen Glitching image - USA style by pimskie (@pimskie) on CodePen. Let's do this! See the Pen Get Out There And Vote by Ruslan Pivovarov (@mrspok407) on CodePen. See the Pen #govote by Adam Kuhn (@cobra_winfrey) on CodePen. We understand that not every person reading this blog lives in the United States or is able to vote for one reason or another and that's cool. If that's you and you're still game to join in the fun, that's awesome. If not, no worries at all and carry on with your good work. Get Out the Vote, CSS Style! is a post from CSS-Tricks

Updated Art Direction Data

Updated Art Direction Data

2009: Smashing Magazine article "20 Extraordinary Blogs With Unique Post Designs" 2013: We tested those URL's to see how they were holding up: 18/40 intact 2016: We tested again: 13/40 intact, even with a few returning from the dead Updated Art Direction Data is a post from CSS-Tricks

Notes from CSS Dev Conf 2016

Notes from CSS Dev Conf 2016

I've attended every single CSS Dev Conf, five years running now. I've always particularly enjoyed conferences that have a somewhat narrow focus. Since this conference is, no surprise, largely focused on CSS, I think it serves as an interesting state of the union of CSS style conference every year, for me at least. In years past themes have emerged like: preprocessors, architecture, testing, performance... last year I'd say it was SVG. Caveat! CSS Dev Conf is a multi-track conference, plus I had to miss the "best of" talks as that's when I was hosting the CodePen Show & Tell. So this isn't wholly representative of the entire conference. Sorry! Come next year! Mike Riethmuller talked about responsive typography Or more accurately: "Advanced Fluid Typography". He also won best of show! Well deserved, I think. He walked us through how calc() works, then started incorporating viewport units and applying everything to font-size. The result can be "fluid type", or type that resizes based on the width of the screen, with minimums and maximums. It's cooler than using viewport units alone (or SVG) because you can limit the scale. See the Pen Precision responsive typography by Mike (@MadeByMike) on CodePen. This can be used instead of using media query breakpoints adjusting the font-size in fixed increments. It's a big deal because: It's similar to the difference between adaptive layouts and responsive layouts. We all know responsive kinda won the day there. Adjusting font-size at breakpoints is kinda janky and not nearly as satisfying as this. It's basically a one-liner of code, so adoption of this idea should be pretty easy. And that's not all! If the type all just scaled proportionally to each other, that'd be kinda like SVG <text>. The limits are a differentiator, but also, the concepts can be extended to: Fluid modular scale: a header might be 1.5× bigger than the next on a large screen but only 1.2× bigger on a small screen (but on a fluid scale!) Fluid vertical rhythm: it's not just the font-size that changes, but line-height and other spacing as well. We integrated it into the blogs on CodePen. David Khourshid talked about Reactive Animations This was some serious CSS trickery. It's all rooted in the ability to manipulate CSS variables with JavaScript. It's like giving CSS deep access to DOM events. The way David implemented his demos was with RxJS and his own helper library RxCSS. His similar talk from a previous conference: Since CSS Dev Conf, David has collected a bunch of wild demos possible through CSS variables. It's also inspired several others to explore the territory, like this: See the Pen CSS Variables As Data Feedback by Jase (@jasesmith) on CodePen. Alicia Sedlock talked about How to Fight Burnout This hugely resonated with the audience. There was lively conversation afterwards with great questions digging deeper into the subject. I learned the term "brownout", which is a more accurate term for being overwhelmed and stepping away, leaving "burnout" for more more serious breaks. Dan Wilson talked about Motion Paths This was a perfect talk for CSS Dev Conf in that it involved cutting edge CSS (they changed the names of these properties, like weeks before this) and it's fun to play with. Dan wasn't talking about SVG motion paths, which are only really possible through SMIL which still seems fated for oblivion. These are paths directly in CSS, like: .thing-that-moves { /* "Old" syntax. Available in Blink browsers as of ~October 2015 */ motion-path: path("M 5 5 m -4, 0 a 4,4 0 1,0 8,0 a 4,4 0 1,0 -8,0"); /* Currently spec'd syntax. Should be in stable Chrome as of ~December 2016 */ offset-path: path("M 5 5 m -4, 0 a 4,4 0 1,0 8,0 a 4,4 0 1,0 -8,0"); } See the Pen Photo Sharer (CSS Motion Path Demo) by Dan Wilson (@danwilson) on CodePen. Rachel Nabors talked about Communicating Animation From her article of the same name. Trent Walson told us about The Seasonal Web Developer Trent talked eloquently about balance. The talks at CSS Dev Conf could be organized by: Philosophical, Adventurous, Constructive, and Restorative. So too, could people in what they are talking about and advocating for. There is strength and reason in all of them, but it can also lead to frustration. These categories even manifest in our work and our communities, where it can also be both frustrating and inspiring. Wes Bos showed Applicable ES6—Tips Wes has a way of driving home concepts in a digestible and light-bulb-activating way. Direct link to talk. Gregor Adams talked about Impressive Art with CSS Gregor is a code artist and talked about web tech and art and geometry and all that wonderful stuff I get immersed in every day by the CodePen community. I love it when Gregor goes on little kicks doing a ton of experimentation around a particular technique or idea. Last year it was fractals. This year we got a taste of many different ideas. See the Pen hexels / trixels drawing app [prototype] by Gregor Adams (@pixelass) on CodePen. Clarissa Peterson talked about Creating Beautiful, Accessible, and User-Friendly Forms I like the emphasis on beautiful here from Clarissa. When advocating for form accessibility, it doesn't need to be at the cost of aesthetics. In fact, well-designed forms with ample spacing, inviting colors, and clear states help accessibility. No shortage of examples in here! Sarah Drasner talked about Creativity in Programming Creativity is fun, but it isn't just fun. It's good for your brain and your business. Cecy Correa helped us Level Up Our GIF Game The talk got deliciously nerdy at the end when Cecy explained how you could build your own GIF library by hosting them for free on GitHub, and then creating a bash alias to search for them and return sharable URL's. alias gif-search="~/absolute/path/to/gif-repo ls | grep" $ gif-search query Eli Fitch talked about Perceived Performance Perceived performance is the most important kind of performance. Eli's emoji analogy made me think of how I'm happy to drive 20 miles out of the way and have it take 8 minutes longer than I am sitting in standstill traffic. One of the mini-subjects that came up in Eli's talk was fake progress bars. During the Show & Tell later, I demonstrated how you might randomize the progress of a (fake) progress bar using the random() function in Sass: See the Pen Randomized Progress Bar Easings by Chris Coyier (@chriscoyier) on CodePen. Michael Cohen talked about Sunsetting Our Grid Systems and Embracing CSS Grid Module I think Michael is right in that, eventually, what we think of a "grid systems" right now will be sunset. I'm excited for grid primarily because I think it will usher in a new era of layout creativity. That's certainly what Jen Simmons is advocating for! Jen Simmons talked about Real Art Direction on the Web Grid layout is definitely a theme this year. But Jen's keynote is about more than just "grid will be cool", she advocates lots of modern tools that help unlock huge potential for creativity: flexbox, shapes, columns, and more. This talk from a previous conference: Rachel Andrew talking about Knowing it All Rachel talks a ton about CSS grid layout, which would have given us a one-two-three punch of that, but Rachel was a keynote speaker and delivered a more personal and historical talk about the web. Fundamentals, y'all. Joah Gerstenberg talked about HTTP/2 Specifically... "How it Impacts Frontend Developers". I wished I could have been there to ask questions, because I definitely have some. Let's say we've already turned on HTTP/2 (I have). Is it already an anti-pattern to be concatenating assets? Are there some example websites out there we can look at to compare before/after performance of when they stopped concatenating assets? If I do stop concatenating assets, is it only-helpful and never-hurtful? Are there any browser/platform/versions out there I'll be hurting? Keith Grant told us to Stop Thinking in Pixels This talk at a previous conference: Relative units are good. Here's more to convince you. Brian Graves talked about Using Tomorrow’s CSS Today "Where we're going, we don't need preprocessors." My favorite: @apply. It's kinda like mixins/extends (in preprocessors) although it doesn't require any duplication of code/selectors to do what it does. See the Pen CSS @apply rule support test by Serg Hospodarets (@malyw) on CodePen. Jonathan Snook talked to us about Responsive Web Apps with Container Queries I sadly missed this and the only link I have for it is the EQCSS ("Element Queries") website. I think "Container Queries" is the more likely name though, maybe? Jonathan will look at the approach the Shopify Admin team used to manage the intricate interactions between all the application’s components, and pull off a fully fluid responsive design in under a month. He’ll also look at how this approach is currently being used at Xero to achieve unity between disparate tech stacks and speed up the development process. It's very interesting to see large-scale production sites going this road. That's what I (wrongly) assumed wasn't happening with container queries. I do still suspect that once there starts to feel like there is some momentum with native browser support for these, it will be a red-hot topic in CSS. Then a real polyfill can be built and the concept will snowball. Kevin Lamping talked about Automated UI Testing CSS testing is another topic that's been around a long time but never seems to get a ton of momentum. It's hard. It's fragile. It's limited in what it can do. Knowing all that ahead of time doesn't make it very appealing to spend a bunch of time on it. I think, personally, I'd have to have a really huge, really mission-critical site, and have a person who's job this was entirely to go down the CSS testing road. It looks like Kevin tackles many of those known challenges, and has a repo up that demonstrates. Matt Vanderpol talked about how to Generate a Living Style Guide from CSS I love how the conversation on style guides has evolved from "have one" to how to make it easy to build and use and manage. Adam McCombs talked about how Pattern Libraries Can Change Your Life Pattern libraries allow you to develop systems not pages. You know what would be a really fun style guide / pattern library talk, to compliment all this smart stuff that is said about them? If people take their style guide, and take a design they need to build, and live-code it using the guide. I think that could nicely showcase the point many people make on how style guides make the final designs faster to build and more consistent. Estelle Weyl talked about Inclusive Code As she says, in other words: Simple. Fast. Accessible. The 1st rule of ARIA use is if you can use a native HTML element or attribute with the semantics behavior already built in, do that instead! — Nasty Woman (@estellevw) February 25, 2016 Ivan Wilson talked about the The Secret Life of Forms There can't be enough evangelizing for doing forms correctly. Vincent Nalupta talked about KonMari CSS KonMari, as in, Marie Kondo's KonMari Method: "The Life-Changing Magic of Tidying Up" (book). Applied to CSS, it's: Analyze, Plan, Refactor, Optimize. Julia Konivestska talked about Data Visualization with Responsive d3.js Part of the talk was about building a chart like this from the ground up: from the data the the libraries and APIs and whatnot to make it all happen. Then making it responsive, which involved measuring things in JavaScript and re-rendering the chart. That's awesome stuff to know, but it also got me thinking about native things you can do in SVG to help the responsive-ness. Some of it came to fruition lately when I was screwing around with Robin's bar chart. Turns out there is a good amount you can do by not using a viewBox at all. If I had to pick a theme or hottest topics, I'd say CSS variables and grid. Notes from CSS Dev Conf 2016 is a post from CSS-Tricks

Demystifying Public Speaking

Demystifying Public Speaking

A new book by Lara Hogan includes some of my favorite advice about public speaking: As you stand on the stage, remember: your audience is anticipating you’ll be successful at giving this talk. To them, everything has been well thought-out and prepared; they walk in assuming (rightly!) they’re going to learn something new or be inspired...and you’re the person who’ll show them how. More, they want you to be successful and are quite forgiving. In my experience, you only lose them once you disrespect them (e.g. "Sorry if I'm not very prepared, I wrote this on the flight over here." annnnnd you've lost me.) Hey while you're over at the A Book Apart store buying this, I heard this one is good. Direct Link to Article — Permalink Demystifying Public Speaking is a post from CSS-Tricks

Improving Perceived Performance with Multiple Background Images

Improving Perceived Performance with Multiple Background Images

.masthead { /* Might as well let this color show */ background-color: #3d332b; /* As this is loading */ background-image: url(/img/masthead.jpg); } Harry Roberts levels this up by suggesting using a gradient that looks more like the image that loads: .masthead { background-image: url(/img/masthead-large.jpg), linear-gradient(to right, #807363 0%, #251d16 50%, #3f302b 75%, #100b09 100%); } Will Wallace gets even fancier by creating a Sass @mixin that takes a big array of colors to make an complex gradient that looks even more like a blurred version of the original. See the Pen "Blurground" gradient function by Will (@wiiiiilllllll) on CodePen. I still the think coolest way to handle it is the "Blur Up" Technique that Emil Björklund covered here. Direct Link to Article — Permalink Improving Perceived Performance with Multiple Background Images is a post from CSS-Tricks

Web fonts, boy, I don’t know

Web fonts, boy, I don’t know

Nothing like a trip through 2G country to get you thinking hard about web performance. Monica Dinosaurescu: Listen: it doesn’t have to be this way. You can lazy load your font. It’s 4 lines of JavaScript. 7 if you’re being ambitious. I still find it fascinating how much we all seemed to hate FOUT, and took steps to fight it, and now are like, "bring back the FOUT!", and largely have. She suggests async loading, which is good, but remember you can take it further. One-step better is "FOUT with a class" from Zach Leatherman's guide, and gets fancier from there. Direct Link to Article — Permalink Web fonts, boy, I don’t know is a post from CSS-Tricks

Bluemix fundamentals: Add a Cloudant NoSQL database to your Node.js             app

Bluemix fundamentals: Add a Cloudant NoSQL database to your Node.js app

Learn how to create and add a Cloudant NoSQL database to your Node.js web applications on Bluemix. Explore a data-driven, dynamically generated web store that changes appearance depending on current inventory stock levels. Use the web-based Cloudant Dashboard to view and maintain your NoSQL databases.

Things I’ve Learned About CSS Grid Layout

Things I’ve Learned About CSS Grid Layout

The following is a guest post by Oliver Williams. Oliver has been working with CSS grid layout and has learned quite a bit along the way. In this article he's going to hop around to different concepts that he's learned on that journey. I like this idea of learning little bite-sized chunks about grid layout, through isolated examples where possible. That makes learning the whole thing a bit less intimidating. CSS grid looks set to land in browsers in early 2017. Until then you will need to enable grid for it to work (except in Firefox Nightly where it is enabled by default). Chrome Canary currently has the best implementation. Meanwhile, Firefox has an invaluable add-on called CSS Grid Inspector, which can display the lines of your grid. It is currently the only browser to have such a tool. In Chrome enter `chrome://flags` into your address bar, find ‘Enable experimental Web Platform features’ and click enable. IE and Edge have old implementations different from the current spec, and are therefore not recommended for experimenting with grid at this point in time. You can't have tetris-shaped grid areas You'll work this out pretty quickly for yourself. Your grid areas can only be rectangles (like the left), not arbitrary polygons (like the right). Grid is designed to be used with flexbox, not instead of it Grid and flexbox can act in similar ways. You may have seen people using flexbox to construct grid systems but that's not what flexbox was designed for. It's worth reading Jake Archibald's blog post Don't use flexbox for overall page layout. A quick way to think about it is: Flexbox is for one dimensional layout (row or column). CSS grid is for two dimensional layout. Or as Rachel Andrews put it: Flexbox is essentially for laying out items in a single dimension – in a row OR a column. Grid is for layout of items in two dimensions – rows AND columns. They can be combined as well. You can turn a grid item into a flex container. You can turn a flex item into a grid. Let's take one useful example. We want to vertically center the text inside a grid item but we want the background (whether a color, a gradient or an image) to cover the items entire grid area. We can use the align-items property with a value of center but now the background doesn't fill the whole area of the item. The default for align-items is stretch — as soon as you set it to any other value, it no longer fills the space. Let's instead leave it set to stretch and turn our grid item into a flex container. .grid { align-items: stretch; } .griditem { display: flex; align-items: center; } Using negative line numbers can be really helpful Imagine a current CSS grid framework with a 12 column grid. At small screens, rather than reducing the number of columns, the content is told to span all twelve columns, giving the impression of being a single full-width column. You could do this same kind of thing with grid: /* For small screens */ .span4, .span6, .spanAll { grid-column-end: span 12; } /* For large screens */ @media (min-width: 650px) { .span4 { grid-column-end: span 4; } .span6 { grid-column-end: span 6; } } There's nothing wrong with this approach. However, with CSS grid, it's just as easy to redefine the number of columns. By using -1, you can be sure your content will always reach the end. /* For small screens */ .span4, .span6, .spanAll { grid-column-end: -1; } For a large screen you may want as many as twelve columns. For a mobile, anywhere between one and four. It's easy to change the grid-template-columns value with a media query. .grid { grid-template-columns: 1fr 1fr; } @media (min-width: 700px) { .grid { grid-template-columns: repeat(12, 1fr); } } There are likely some elements we want to span across the whole viewport for all screen sizes, like perhaps the header, footer, or some hero images. For small screens we could write: .wide { grid-column: 1 / 3; /* start at 1, end at 3 */ } However, once we make our media query these elements will cover only the first two columns of twelve columns. We could include the new desired grid-column-end value of 13 in the same media query but there's a far easier method. Just set the end value to -1 and it will span all columns, however many there happen to be. Like: .wide, .hero, .header, .footer { grid-column: 1 / -1; } See the Pen Easier media queries with -1 by CSS GRID (@cssgrid) on CodePen. Grid areas create implicit line names Named grid template areas and grid line numbers are two ways of placing content on the grid but you can use both placement methods in the same grid. You can also make use of the implicit line names that are created whenever you designate a grid-area. .grid { grid-template-areas: "main main sidebar sidebar"; } With this code we just created four implicit line names: main-start, main-end, sidebar-start, and sidebar-end. This could be useful if you want to overlap content either over several grid areas or in a particular subsection of one grid area. See the Pen implicit line names with grid areas by CSS GRID (@cssgrid) on CodePen. There is a second way of defining grid areas Just like grid areas create line names, specific line names can create grid areas. The syntax for defining grid areas looks like: .grid { grid-template-areas: "header header header" "main main sidebar" "footer footer footer"; } This syntax can become somewhat unwieldy if you have a lot of empty space in your design (Too many periods! Periods, in lieu of a name, signify an empty cell.) You may not know that there is another way to define grid areas. We can name grid lines whatever we like. However, if we follow the naming convention [name-start] and [name-end] we can create grid areas. Here's an example: .grid { display: grid; grid-template-columns: 20px 100px [main-start] 1fr [main-end] 100px 20px; grid-template-rows: 100px [main-start] 100px [main-end] 100px; } .griditem1 { background-color: red; grid-area: main; } See the Pen Another way of defining grid-areas by CSS GRID (@cssgrid) on CodePen. You probably wouldn't lay out a whole page of content with this method, but if you want to combine grid-area placement with line number based placement, it's nice to know about. Use vmin for an equal sized box layout CSS grid allows you to use any size unit for your rows and columns. Want equally-sized boxes but also want them to be responsive? If you want content that resizes with the container, you can use viewport units. .grid { grid-template-columns: repeat(5, 20vw); grid-template-rows: repeat(5, 20vh); } This would work perfectly well on desktop and laptop but on mobile phones where the height of the screen exceeds the width, our content will spill over creating a horizontal scroll bar. Dudley Storey recently wrote a blog post about the usefulness of a lesser-known css unit: vmin. This unit will be a percentage of the viewport width on a portrait-oriented screen and a percentage of the viewport height on a landscape-oriented screen. .gridcontainer { display: grid; width: 100vw; height: 100vh; justify-content: center; align-content: center; grid-template-columns: repeat(5, 20vmin); grid-template-rows: repeat(5, 20vmin); } We now have equally-sized boxes that adapt to any screensize. See the Pen Boxy Layout with CSS Grid and vmin by CSS GRID (@cssgrid) on CodePen. Absolute positioning When you absolutely position a grid item, rather than its positioning context being its container (i.e. the entire grid) we can position it in relation to its specified grid-column and grid-row start and end lines. As usual, position: absolute removes an element from the flow of the document (i.e. it is ignored by other elements). This makes absolute positioning useful if you want to overlap grid items without disrupting the grids auto-placement algorithm. Auto-placement goes out of its way not to overlap items unless you explicitly declare both a grid-column-start and grid-row-start value for every item. Try deleting position: absolute; from the div in this example and think about how many items you'd have to define grid-column and grid-row properties for! See the Pen preserving auto-placement with position: absolute by CSS GRID (@cssgrid) on CodePen. Order works differently from how you might think If you've used the flexbox order property, then you already have this covered. All grid items have a default order value of 0. So order: 1; applied to a grid item would make after everything else, not before. You can use negative numbers to push items to the front. See the Pen Order value by CSS GRID (@cssgrid) on CodePen. The limits of minmax Do you want columns that equally divide space between themselves until they reach a maximum width? You might think you could use minmax() like so: .grid { display: grid; grid-template-columns: repeat(3, minmax(1fr, 300px)); } Unfortunately that isn't going to work. If max is less in value than min, it is ignored. The fr is invalid as a minimum value in minmax(). However, it's a very easy behaviour to achieve. The word auto, when used as a value in grid-template-columns or grid-template-rows, will make the column or row as large as its content. See the Pen The value of auto vs fr by CSS GRID (@cssgrid) on CodePen. Then we can set a max-width on the content: .grid { display: grid; grid-template-columns: repeat(3, auto); } .item { max-width: 300px; } See the Pen The limits of minmax by CSS GRID (@cssgrid) on CodePen. There might be good reasons for the way minmax() works and use cases I haven't thought of. Even so, I wrote an entire post on Medium entitled The One Thing I Hate About Grid. Things are way easier if you name your grid lines There are multiple approaches you can take. If you're really into typing, you can give lines multiple names. .grid { grid-template-columns: [col1-start] 1fr [col1-end col2-start] 1fr [col2-end]; } The easiest naming convention makes use of the grids auto-numbering. We don't have to type [col2], we can just type the name col followed by a number. .griditem1 { grid-column-start: col 2; } Combined with the span keyword we can stop thinking about line numbers and start thinking in terms of the first column we want our content to fill and how many columns we want to take up, which is far more intuitive. .grid { grid-template-columns: repeat(4, [col] 100px); } .griditem1 { grid-column: col 2 / span 2; } The fr unit removes the need for math Let's say we want to split our grid into four equal columns. Its easy to work out the percentages. We just write grid-template-columns: 25% 25% 25% 25%. What if we then want to make use of the grid-gap property? grid-gap: 10px. We have three gaps between our columns, so our grid width will now be 100% + 30px and unwanted horizontal scrolling will appear with some content overspilling the screen on the right side. We could make use of calc to solve our problem, but using the fr unit is far easier: grid-template-columns: 1fr 1fr 1fr 1fr. See the Pen fr unit vs percentage by CSS GRID (@cssgrid) on CodePen. The second thing I hate about grid There is no way to force the auto-placement algorithm to leave some columns or rows empty The grid-gap values provides an easy way for spacing our content. While we can specify a separate value to divide our columns and our rows with grid-row-gap and grid-column-gap, these values must be uniform. If we want to separate our first and second row by ten pixels and our second and third row by 50 pixels, grid offers us no way of doing this, other than by creating rows and keeping them empty.You may have seen the periods/dots in the grid-template-area syntax: grid-template-rows: "header header header" "main main main" " . . ." "secondary secondary secondary" "footer footer footer"; This could have provided a simple way to control the auto-placement algorithm to stop it from placing items in these areas. Unfortunately it doesn't work like that: this syntax simply denotes that we don't want to turn the third row into a named grid-area. Auto-placed items will still end up there. Some design advice: You don't necessarily need 12 columns (and columns need not be uniform in size) Twelve columns is the default of web design. The Bootstrap grid uses 12 columns and just about every other grid framework currently out there. There is a good reason for this: twelve is divisible by both three and four, giving more flexibility in how we can lay out content across the page. We can divide our content evenly into 12 parts, 6 parts, 4 parts, 3 parts, or in half. While some people like the familiarity that comes with consistently using the same grid for every project, there is no need to have more columns than you actually need and you should build the grid that's right for your content and desired layout rather than a one-size-fits all set-up. Take a look at the examples on Gridset. Gridset is a useful tool for making grids, but once the native CSS grid module lands you won't need tools like this. It does showcase some excellently-designed grids. I took the liberty of remaking one using the CSS grids: See the Pen text layout with CSS Grid module by CSS GRID (@cssgrid) on CodePen. Things I’ve Learned About CSS Grid Layout is a post from CSS-Tricks

A Handmade SVG Bar Chart (featuring some SVG positioning gotchas)

A Handmade SVG Bar Chart (featuring some SVG positioning gotchas)

Let's take a look at some things I learned about positioning elements within SVG that I discovered whilst making a (seemingly) simple bar chart earlier this week. You don't have much choice but to position things in SVG. SVG is a declarative graphics format, and what are graphics but positioned drawing instructions? There are plenty of gotchas and potentially frustrating situations though, so let's get down to it. This is the bar chart we're wanting to build: We could make this in our graphics editor of choice and dump it into our markup as an <img> tag (even as an `.svg`), but where would the fun in that be? I also wanted to make this SVG chart by hand because I knew I would learn more about the syntax than if I were to just export a Sketch or Illustrator file. To get things moving, let’s make an <svg> that will house our child elements: <svg width='100%' height='65px'> </svg> Now let’s make the two bars. The first will sit in the background and the second will sit on top of it and it’ll represent the data of our graph: <svg width='100%' height='65px'> <g class='bars'> <rect fill='#3d5599' width='100%' height='25'></rect>; <rect fill='#cb4d3e' width='45%' height='25'></rect> </g> </svg> (Without providing a x and y attribute for the <rect>s, they are assumed to be 0.) In the demo below, I’ve made them animate so you can see the second rect is placed on top of the first (this would be like drawing two rectangles in Sketch, one is layered on top of the other): See the Pen Chart stuff: 1 by Robin Rendle (@robinrendle) on CodePen. Next we can add the lines that act as markers to more easily read the data at 0, 25, 50, 75, and 100%. All we need to do is make a new group and add a rect for each marker, right? Sure, but you'll see we run into a little problem in a second. Inside the SVG, and beneath the <g> where we styled our chart’s data we can write the following: <g class='markers'> <rect fill='red' x='50%' y='0' width='2' height='35'></rect> </g> That ought to look like this: See the Pen Chart stuff: 2 by Robin Rendle (@robinrendle) on CodePen. Neat! Let’s add all the other markers and fix the colors whilst we’re at it: <g class='markers'> <rect fill='#001f3f' x='0%' y='0' width='2' height='35'></rect> <rect fill='#001f3f' x='25%' y='0' width='2' height='35'></rect> <rect fill='#001f3f' x='50%' y='0' width='2' height='35'></rect> <rect fill='#001f3f' x='75%' y='0' width='2' height='35'></rect> <rect fill='#001f3f' x='100%' y='0' width='2' height='35'></rect> </g> We’ve added a rect for each marker, added a fill to set the color and positioned them with the x attribute. Let’s see what that renders in the browser: See the Pen Chart stuff: 3 by Robin Rendle (@robinrendle) on CodePen. Where the heck did that last one go? Well, we did tell it to be positioned at 100%, so it's actually positioned off the screen to the right. We need to take its width into consideration and move it to the left by 2. There are a number of ways we can fix this. 1) We could apply an inline transform to nudge it back over <rect fill='#001f3f' x='100%' y='0' width='2' height='35' transform="translate(-2, 0)"></rect> 2) We could apply that same transform through CSS: rect:last-of-type { transform: translateX(-2px); /* Remember this isn't really "pixels", it's a length of 2 in the SVG coordinate system */ } 3) Or we could not use a percentage and place it along the X axis at a precise place. We know what the exact coordinate system of SVG is thanks to the viewBox attribute. In Chapter 6 of Practical SVG, Chris explains: The viewBox is an attribute of svg that determines the coordinate system and aspect ratio. The four values are x, y, width and height. Say our viewBox is... <svg viewBox='0 0 1000 65'> <!-- the rest of our svg code goes here --> </svg> So it is 1000 units wide. Our marker is 2 units wide. So to place the final marker along the right edge, we'd place it at 998! (1000 - 2). That becomes our x attribute: <svg viewBox='0 0 1000 65'> ... <rect fill='#001f3f' x='998' y='0' width='2' height='35'></rect> ... </svg> Which will lead to our marker being positioned on the far right edge of the SVG, even if we resize it: See the Pen Chart stuff: 4 by Robin Rendle (@robinrendle) on CodePen. Yay! We don’t have to add a % or a pixel value here because it’s using the coordinate system set up by the viewBox. With all that finally sorted we can move onto the next issue: adding the % label text beneath each marker to denote 25, 50%, etc. To do that we’ll make a new <g> inside the <svg> and add our <text> elements: <g> <text fill='#0074d9' x='0' y='60'>0%</text> <text fill='#0074d9' x='25%' y='60'>25%</text> <text fill='#0074d9' x='50%' y='60'>50%</text> <text fill='#0074d9' x='75%' y='60'>75%</text> <text fill='#0074d9' x='100%' y='60'>100%</text> </g> Because we’re doing this by hand I want to use % for the x value, but unfortunately that will end up looking like this: See the Pen Chart stuff: 5 by Robin Rendle (@robinrendle) on CodePen. Again, we have a problem with the final element not aligning as we might expect it to. The middle labels are misplaced as well; ideally they would be centered underneath their marks. I thought I might have to nudge each element into place with the correct x value before Chris pointed me to the text-anchor attribute which I had never heard about. With this attribute we can manipulate the text sort of like the text-align property in CSS. This property is naturally inherited, so we can set it once on the g and then target the first and last elements: <g text-anchor='middle'> <text text-anchor='start' fill='#0074d9' x='0' y='60'>0%</text> <text fill='#0074d9' x='25%' y='60'>25%</text> <text fill='#0074d9' x='50%' y='60'>50%</text> <text fill='#0074d9' x='75%' y='60'>75%</text> <text text-anchor='end' fill='#0074d9' x='100%' y='60'>100%</text> </g> That will look like: See the Pen Chart stuff: 6 by Robin Rendle (@robinrendle) on CodePen. That’s it! With a little bit of knowledge about how viewBox works, along with x, y coordinates and attributes like text-anchor we can do pretty much anything with the elements inside an SVG. By making these charts by hand, it feels like we have a lot of control. It's not hard to imagine how we might exert even more design control or pipe in data with JavaScript. With a tiny extra bit of work, we can animate these charts to really make them stand out. Try hovering over this version of our chart for instance: See the Pen Chart stuff: 7 by Robin Rendle (@robinrendle) on CodePen. Pretty neat, huh? And that’s all possible with just SVG and CSS. If you’re interested in learning more then I wrote about how to make charts with SVG a while back which explains this stuff in more depth. Now let’s go make some cool charts! A Handmade SVG Bar Chart (featuring some SVG positioning gotchas) is a post from CSS-Tricks