This is the fourth post in a series about the new CSS shape()
function. So far, we’ve covered the most common commands you will use to draw various shapes, including lines, arcs, and curves. This time, I want to introduce you to two more commands: close
and move
. They’re fairly simple in practice, and I think you will rarely use them, but they are incredibly useful when you need them.
Better CSS Shapes Using shape()
- Lines and Arcs
- More on Arcs
- Curves
- Close and Move (you are here!)
The close
command
In the first part, we said that shape()
always starts with a from
command to define the first starting point but what about the end? It should end with a close
command.
But you never used any
close
command in the previous articles!?
That’s true. I never did because I either “close” the shape myself or rely on the browser to “close” it for me. Said like that, it’s a bit confusing, but let’s take a simple example to better understand:
clip-path: shape(from 0 0, line to 100% 0, line to 100% 100%)
If you try this code, you will get a triangle shape, but if you look closely, you will notice that we have only two line commands whereas, to draw a triangle, we need a total of three lines. The last line between 100% 100%
and 0 0
is implicit, and that’s the part where the browser is closing the shape for me without having to explicitly use a close
command.
I could have written the following:
clip-path: shape(from 0 0, line to 100% 0, line to 100% 100%, close)
Or instead, define the last line by myself:
clip-path: shape(from 0 0, line to 100% 0, line to 100% 100%, line to 0 0)
But since the browser is able to close the shape alone, there is no need to add that last line
command nor do we need to explicitly add the close
command.
This might lead you to think that the close
command is useless, right? It’s true in most cases (after all, I have written three articles about shape()
without using it), but it’s important to know about it and what it does. In some particular cases, it can be useful, especially if used in the middle of a shape.
In this example, my starting point is the center and the logic of the shape is to draw four triangles. In the process, I need to get back to the center each time. So, instead of writing line to center
, I simply write close
and the browser will automatically get back to the initial point!
Intuitively, we should write the following:
clip-path: shape(
from center,
line to 20% 0, hline by 60%, line to center, /* triangle 1 */
line to 100% 20%, vline by 60%, line to center, /* triangle 2 */
line to 20% 100%, hline by 60%, line to center, /* triangle 3 */
line to 0 20%, vline by 60% /* triangle 4 */
)
But we can optimize it a little and simply do this instead:
clip-path: shape(
from center,
line to 20% 0, hline by 60%, close,
line to 100% 20%, vline by 60%, close,
line to 20% 100%, hline by 60%, close,
line to 0 20%, vline by 60%
)
We write less code, sure, but another important thing is that if I update the center
value with another position, the close
command will follow that position.
Don’t forget about this trick. It can help you optimize a lot of shapes by writing less code.
The move
command
Let’s turn our attention to another shape()
command you may rarely use, but can be incredibly useful in certain situations: the move
command.
Most times when we need to draw a shape, it’s actually one continuous shape. But it may happen that our shape is composed of different parts not linked together. In these situations, the move
command is what you will need.
Let’s take an example, similar to the previous one, but this time the triangles don’t touch each other:
Intuitively, we may think we need four separate elements, with its own shape()
definition. But the that example is a single shape!
The trick is to draw the first triangle, then “move” somewhere else to draw the next one, and so on. The move
command is similar to the from
command but we use it in the middle of shape()
.
clip-path: shape(
from 50% 40%, line to 20% 0, hline by 60%, close, /* triangle 1 */
move to 60% 50%, line to 100% 20%, vline by 60%, close, /* triangle 2 */
move to 50% 60%, line to 20% 100%, hline by 60%, close, /* triangle 3 */
move to 40% 50%, line to 0 20%, vline by 60% /* triangle 4 */
)
After drawing the first triangle, we “close” it and “move” to a new point to draw the next triangle. We can have multiple shapes using a single shape()
definition. A more generic code will look like the below:
clip-path: shape(
from X1 Y1, ..., close, /* shape 1 */
move to X2 Y2, ..., close, /* shape 2 */
...
move to Xn Yn, ... /* shape N */
)
The close
commands before the move
commands aren’t mandatory, so the code can be simplified to this:
clip-path: shape(
from X1 Y1, ..., /* shape 1 */
move to X2 Y2, ..., /* shape 2 */
...
move to Xn Yn, ... /* shape N */
)
Let’s look at a few interesting use cases where this technique can be helpful.
Cut-out shapes
Previously, I shared a trick on how to create cut-out shapes using clip-path: polygon()
. Starting from any kind of polygon, we can easily invert it to get its cut-out version:
We can do the same using shape()
. The idea is to have an intersection between the main shape and the rectangle shape that fits the element boundaries. We need two shapes, hence the need for the move
command.
The code is as follows:
.shape {
clip-path: shape(from ...., move to 0 0, hline to 100%, vline to 100%, hline to 0);
}
You start by creating your main shape and then you “move” to 0 0
and you create the rectangle shape (Remember, It’s the first shape we create in the first part of this series). We can even go further and introduce a CSS variable to easily switch between the normal shape and the inverted one.
.shape {
clip-path: shape(from .... var(--i,));
}
.invert {
--i:,move to 0 0, hline to 100%, vline to 100%, hline to 0;
}
By default, --i
is not defined so var(--i,)
will be empty and we get the main shape. If we define the variable with the rectangle shape, we get the inverted version.
Here is an example using a rounded hexagon shape:
In reality, the code should be as follows:
.shape {
clip-path: shape(evenodd from .... var(--i,));
}
.invert {
--i:,move to 0 0, hline to 100%, vline to 100%, hline to 0;
}
Notice the evenodd
I am adding at the beginning of shape()
. I won’t bother you with a detailed explanation on what it does but in some cases, the inverted shape is not visible and the fix is to add evenodd
at the beginning. You can check the MDN page for more details.
Another improvement we can do is to add a variable to control the space around the shape. Let’s suppose you want to make the hexagon shape of the previous example smaller. It‘s tedious to update the code of the hexagon but it’s easier to update the code of the rectangle shape.
.shape {
clip-path: shape(evenodd from ... var(--i,)) content-box;
}
.invert {
--d: 20px;
padding: var(--d);
--i: ,move to calc(-1*var(--d)) calc(-1*var(--d)),
hline to calc(100% + var(--d)),
vline to calc(100% + var(--d)),
hline to calc(-1*var(--d));
}
We first update the reference box of the shape to be content-box
. Then we add some padding which will logically reduce the area of the shape since it will no longer include the padding (nor the border). The padding is excluded (invisible) by default and here comes the trick where we update the rectangle shape to re-include the padding.
That is why the --i
variable is so verbose. It uses the value of the padding to extend the rectangle area and cover the whole element as if we didn’t have content-box
.
Not only you can easily invert any kind of shape, but you can also control the space around it! Here is another demo using the CSS-Tricks logo to illustrate how easy the method is:
This exact same example is available in my SVG-to-CSS converter, providing you with the shape()
code without having to do all of the math.
Repetitive shapes
Another interesting use case of the move
command is when we need to repeat the same shape multiple times. Do you remember the difference between the by
and the to
directives? The by
directive allows us to define relative coordinates considering the previous point. So, if we create our shape using only by
, we can easily reuse the same code as many times as we want.
Let’s start with a simple example of a circle shape:
clip-path: shape(from X Y, arc by 0 -50px of 1%, arc by 0 50px of 1%)
Starting from X Y
, I draw a first arc moving upward by 50px
, then I get back to X Y
with another arc using the same offset, but downward. If you are a bit lost with the syntax, try reviewing Part 1 to refresh your memory about the arc
command.
How I drew the shape is not important. What is important is that whatever the value of X Y
is, I will always get the same circle but in a different position. Do you see where I am going with this idea? If I want to add another circle, I simply repeat the same code with a different X Y
.
clip-path: shape(
from X1 Y1, arc by 0 -50px of 1%, arc by 0 50px of 1%,
move to X2 Y2, arc by 0 -50px of 1%, arc by 0 50px of 1%
)
And since the code is the same, I can store the circle shape into a CSS variable and draw as many circles as I want:
.shape {
--sh:, arc by 0 -50px of 1%, arc by 0 50px of 1%;
clip-path: shape(
from X1 Y1 var(--sh),
move to X2 Y2 var(--sh),
...
move to Xn Yn var(--sh)
)
}
You don’t want a circle? Easy, you can update the --sh
variable with any shape you want. Here is an example with three different shapes:
And guess what? You can invert the whole thing using the cut-out technique by adding the rectangle shape at the end:
This code is a perfect example of the shape()
function’s power. We don’t have any code duplication and we can simply adjust the shape with CSS variables. This is something we are unable to achieve with the path()
function because it doesn’t support variables.
Conclusion
That’s all for this fourth installment of our series on the CSS shape()
function! We didn’t make any super complex shapes, but we learned how two simple commands can open a lot of possibilities of what can be done using shape()
.
Just for fun, here is one more demo recreating a classic three-dot loader using the last technique we covered. Notice how much further we could go, adding things like animation to the mix:
Better CSS Shapes Using shape()
- Lines and Arcs
- More on Arcs
- Curves
- Close and Move (you are here!)
Better CSS Shapes Using shape() — Part 4: Close and Move originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
from CSS-Tricks https://ift.tt/60d1sqr
via IFTTT
No comments:
Post a Comment