Tag: path

[David Baron’s] Thoughts on an implementable path forward for Container Queries

That’s the title of a public post from David Baron, a Principal Engineer at Firefox, with thoughts toward container queries. I know a lot of people have been holding their breath waiting for David’s ideas, as he’s one of few uniquely qualified to understand the ins and outs of this and speak to implementation possibility.

We’re still in the early stages of container queries. Every web designer and developer wants them, the browsers know it, but it’s a super complicated situation. It was very encouraging in February 2020 to hear positive signals about a possible switch-statement syntax that would give us access to an available-inline-size used to conditionally set individual values.

Now we’re seeing a second idea that is also in the realm of the possible.

This ideas uses an @rule instead for the syntax. From the document:

@container <selector> (<container-media-query>)? {   // ... rules ... }

So I’m imagining it like:

.parent {   contain: layout inline-size;   display: grid;   grid-template-columns: 100%;   gap: 1rem; } @container .parent (min-width: 400px) {   grid-template-columns: 1fr 1fr;    .child::before {      content: "Hello from container query land!";    } }


  1. I’m not sure if you’d have to repeat the selector inside as well? Or if dropping property/value pairs in there automatically applies to the selector in the @rule.
  2. David says “The rules can match only that container’s descendants. Probably we’d need support for some properties applying to the container itself, but others definitely can’t.” I’d hope grid properties are a strong contender for something you can change, but I have no idea. Otherwise, I think we’d see people wrapping elements with <div class="container-query"> to get around the “only descendants” limit.

Containment seems to be a very important part of this. Like if the element isn’t property contained, the container query just won’t work. I don’t know that much about containment, but Rachel has a great deep dive from late last year.

Again, this is super early days, I’m just having fun watching this and none of us really have any idea what will actually make it to browsers.

Direct Link to ArticlePermalink

The post [David Baron’s] Thoughts on an implementable path forward for Container Queries appeared first on CSS-Tricks.


, , , , , , ,

Different Approaches to Responsive CSS Motion Path

As a follow-up to Jhey’s recent post on responsive motion paths, Michelle Barker notes that another approach could be to just transform: scale() the whole dang element.

The trade-off there is that you’re scaling both the path and the element on the path at the same time; Jhey’s approach only makes path flexbile and the element stays the same size.

Calculating scale is a really cool trick I think and one we’ve also covered before.

Direct Link to ArticlePermalink

The post Different Approaches to Responsive CSS Motion Path appeared first on CSS-Tricks.


, , , ,

Create a Responsive CSS Motion Path? Sure We Can!

There was a discussion recently on the Animation at Work Slack: how could you make a CSS motion path responsive? What techniques would be work? This got me thinking.

A CSS motion path allows us to animate elements along custom user-defined paths. Those paths follow the same structure as SVG paths. We define a path for an element using offset-path.

.block {   offset-path: path('M20,20 C20,100 200,0 200,100'); }

These values appear relative at first and they would be if we were using SVG. But, when used in an offset-path, they behave like px units. This is exactly the problem. Pixel units aren’t really responsive. This path won’t flex as the element it is in gets smaller or larger. Let’s figure this out.

To set the stage, the offset-distance property dictates where an element should be on that path:

Not only can we define the distance an element is along a path, but we can also define an element’s rotation with offset-rotate. The default value is auto which results in our element following the path. Check out the property’s almanac article for more values.

To animate an element along the path, we animate the offset-distance:

OK, that catches up to speed on moving elements along a path. Now we have to answer…

Can we make responsive paths?

The sticking point with CSS motion paths is the hardcoded nature. It’s not flexible. We are stuck hardcoding paths for particular dimensions and viewport sizes. A path that animates an element 600px, will animate that element 600px regardless of whether the viewport is 300px or 3440px wide.

This differs from what we are familiar with when using SVG paths. They will scale with the size of the SVG viewbox.

Try resizing this next demo below and you’ll see:

  • The SVG will scale with the viewport size as will the contained path.
  • The offset-path does not scale and the element goes off course.

This could be okay for simpler paths. But once our paths become more complicated, it will be hard to maintain. Especially if we wish to use paths we’ve created in vector drawing applications.

For example, consider the path we worked with earlier:

.element {   --path: 'M20,20 C20,100 200,0 200,100';   offset-path: path(var(--path)); }

To scale that up to a different container size, we would need to work out the path ourselves, then apply that path at different breakpoints. But even with this “simple” path, is it a case of multiplying all the path values? Will that give us the right scaling?

@media(min-width: 768px) {   .element {     --path: 'M40,40 C40,200 400,0 400,200'; // ????   } }

A more complex path such as one drawn in a vector application is going to be trickier to maintain. It will need the developer to open the application, rescale the path, export it, and integrate it with the CSS. This will need to happen for all container size variations. It’s not the worst solution, but it does require a level of maintenance that we might not want to get ourselves into.

.element {   --path: 'M40,228.75L55.729166666666664,197.29166666666666C71.45833333333333,165.83333333333334,102.91666666666667,102.91666666666667,134.375,102.91666666666667C165.83333333333334,102.91666666666667,197.29166666666666,165.83333333333334,228.75,228.75C260.2083333333333,291.6666666666667,291.6666666666667,354.5833333333333,323.125,354.5833333333333C354.5833333333333,354.5833333333333,386.0416666666667,291.6666666666667,401.7708333333333,260.2083333333333L417.5,228.75';   offset-path: path(var(--path)); } 
 @media(min-width: 768px) {   .element {     --path: 'M40,223.875L55.322916666666664,193.22916666666666C70.64583333333333,162.58333333333334,101.29166666666667,101.29166666666667,131.9375,101.29166666666667C162.58333333333334,101.29166666666667,193.22916666666666,162.58333333333334,223.875,223.875C254.52083333333334,285.1666666666667,285.1666666666667,346.4583333333333,315.8125,346.4583333333333C346.4583333333333,346.4583333333333,377.1041666666667,285.1666666666667,392.4270833333333,254.52083333333334L407.75,223.875';   } } 
 @media(min-width: 992px) {   .element {     --path: 'M40,221.625L55.135416666666664,191.35416666666666C70.27083333333333,161.08333333333334,100.54166666666667,100.54166666666667,130.8125,100.54166666666667C161.08333333333334,100.54166666666667,191.35416666666666,161.08333333333334,221.625,221.625C251.89583333333334,282.1666666666667,282.1666666666667,342.7083333333333,312.4375,342.7083333333333C342.7083333333333,342.7083333333333,372.9791666666667,282.1666666666667,388.1145833333333,251.89583333333334L403.25,221.625';   } }

It feels like a JavaScript solution makes sense here. GreenSock is my first thought because its MotionPath plugin can scale SVG paths. But what if we want to animate outside of an SVG? Could we write a function that scales the paths for us? We could but it won’t be straightforward.

Trying different approaches

What tool allows us to define a path in some way without the mental overhead? A charting library! Something like D3.js allows us to pass in a set of coordinates and receive a generated path string. We can tailor that string to our needs with different curves, sizing, etc.

With a little tinkering, we can create a function that scales a path based on a defined coordinate system:

This definitely works, but it’s also less than ideal because it’s unlikely we are going to be declaring SVG paths using sets of coordinates. What we want to do is take a path straight out of a vector drawing application, optimize it, and drop it on a page. That way, we can invoke some JavaScript function and let that do the heavy lifting.

So that’s exactly what we are going to do.

First, we need to create a path. This one was thrown together quickly in Inkscape. Other vector drawing tools are available.

A path created in Inkscape on a 300×300 canvas

Next, let’s optimize the SVG. After saving the SVG file, we’ll run it through Jake Archibald’s brilliant SVGOMG tool. That gives us something along these lines:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 79.375 79.375" height="300" width="300"><path d="M10.362 18.996s-6.046 21.453 1.47 25.329c10.158 5.238 18.033-21.308 29.039-18.23 13.125 3.672 18.325 36.55 18.325 36.55l12.031-47.544" fill="none" stroke="#000" stroke-width=".265"/></svg>

The parts we’re interested are path and viewBox.

Expanding the JavaScript solution

Now we can create a JavaScript function to handle the rest. Earlier, we created a function that takes a set of data points and converts them into a scalable SVG path. But now we want to take that a step further and take the path string and work out the data set. This way our users never have to worry about trying to convert their paths into data sets.

There is one caveat to our function: Besides the path string, we also need some bounds by which to scale the path against. These bounds are likely to be the third and fourth values of the viewBox attribute in our optimized SVG.

const path = "M10.362 18.996s-6.046 21.453 1.47 25.329c10.158 5.238 18.033-21.308 29.039-18.23 13.125 3.672 18.325 36.55 18.325 36.55l12.031-47.544"; const height = 79.375 // equivalent to viewbox y2 const width = 79.375 // equivalent to viewbox x2 
 const motionPath = new ResponsiveMotionPath({   height,   width,   path, });

We won’t go through this function line-by-line. You can check it out in the demo! But we will highlight the important steps that make this possible.

First, we’re converting a path string into a data set

The biggest part of making this possible is being able to read the path segments. This is totally possible, thanks to the SVGGeometryElement API. We start by creating an SVG element with a path and assigning the path string to its d attribute.

// To convert the path data to points, we need an SVG path element. const svgContainer = document.createElement('div'); // To create one though, a quick way is to use innerHTML svgContainer.innerHTML = `   <svg xmlns="http://www.w3.org/2000/svg">     <path d="$ {path}" stroke-width="$ {strokeWidth}"/>   </svg>`; const pathElement = svgContainer.querySelector('path');

Then we can use the SVGGeometryElement API on that path element. All we need to do is iterate over the total length of the path and return the point at each length of the path.

convertPathToData = path => {   // To convert the path data to points, we need an SVG path element.   const svgContainer = document.createElement('div');   // To create one though, a quick way is to use innerHTML   svgContainer.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg">                               <path d="$ {path}"/>                             </svg>`;   const pathElement = svgContainer.querySelector('path');   // Now to gather up the path points.   const DATA = [];   // Iterate over the total length of the path pushing the x and y into   // a data set for d3 to handle 👍   for (let p = 0; p < pathElement.getTotalLength(); p++) {     const { x, y } = pathElement.getPointAtLength(p);     DATA.push([x, y]);   }   return DATA; }

Next, we generate scaling ratios

Remember how we said we’d need some bounds likely defined by the viewBox? This is why. We need some way to calculate a ratio of the motion path against its container. This ratio will be equal to that of the path against the SVG viewBox. We will then use these with D3.js scales.

We have two functions: one to grab the largest x and y values, and another to calculate the ratios in relation to the viewBox.

getMaximums = data => {   const X_POINTS = data.map(point => point[0])   const Y_POINTS = data.map(point => point[1])   return [     Math.max(...X_POINTS), // x2     Math.max(...Y_POINTS), // y2   ] } getRatios = (maxs, width, height) => [maxs[0] / width, maxs[1] / height]

Now we need to generate the path

The last piece of the puzzle is to actually generate the path for our element. This is where D3.js actually comes into play. Don’t worry if you haven’t used it before because we’re only using a couple of functions from it. Specifically, we are going to use D3 to generate a path string with the data set we generated earlier.

To create a line with our data set, we do this:

d3.line()(data); // M10.362000465393066,18.996000289916992L10.107386589050293, etc.

The issue is that those points aren’t scaled to our container. The cool thing with D3 is that it provides the ability to create scales. These act as interpolation functions. See where this is going? We can write one set of coordinates and then have D3 recalculate the path. We can do this based on our container size using the ratios we generated.

For example, here’s the scale for our x coordinates:

const xScale = d3   .scaleLinear()   .domain([     0,     maxWidth,   ])   .range([0, width * widthRatio]);

The domain is from 0 to our highest x value. The range in most cases will go from 0 to container width multiplied by our width ratio.

There are times where our range may differ and we need to scale it. This is when the aspect ratio of our container doesn’t match that of our path. For example, consider a path in an SVG with a viewBox of 0 0 100 200. That’s an aspect ratio of 1:2. But if we then draw this in a container that has a height and width of 20vmin, the aspect ratio of the container is 1:1. We need to pad the width range to keep the path centered and maintain the aspect ratio.

What we can do in these cases is calculate an offset so that our path will still be centered in our container. 

const widthRatio = (height - width) / height const widthOffset = (ratio * containerWidth) / 2 const xScale = d3   .scaleLinear()   .domain([0, maxWidth])   .range([widthOffset, containerWidth * widthRatio - widthOffset])

Once we have two scales, we can map our data points using the scales and generate a new line.

const SCALED_POINTS = data.map(POINT => [   xScale(POINT[0]),   yScale(POINT[1]), ]); d3.line()(SCALED_POINTS); // Scaled path string that is scaled to our container

We can apply that path to our element by passing it inline via a CSS property 👍

ELEMENT.style.setProperty('--path', `"$ {newPath}"`);

Then it’s our responsibility to decide when we want to generate and apply a new scaled path. Here’s one possible solution:

const setPath = () => {   const scaledPath = responsivePath.generatePath(     CONTAINER.offsetWidth,     CONTAINER.offsetHeight   )   ELEMENT.style.setProperty('--path', `"$ {scaledPath}"`) } const SizeObserver = new ResizeObserver(setPath) SizeObserver.observe(CONTAINER)

This demo (viewed best in full screen) shows three versions of the element using a motion path. The paths are present to easier see the scaling. The first version is the unscaled SVG. The second is a scaling container illustrating how the path doesn’t scale. The third is using our JavaScript solution to scale the path.

Phew, we did it!

This was a really cool challenge and I definitely learned a bunch from it! Here’s a couple of demos using the solution.

It should work as a proof of concept and looks promising! Feel free to drop your own optimized SVG files into this demo to try them out! — it should catch most aspect ratios.

I’ve created a package named “Meanderer” on GitHub and npm. You can also pull it down with unpkg CDN to play with it in CodePen, if you want to try it out.

I look forward to seeing where this might go and hope we might see some native way of handling this in the future. 🙏

The post Create a Responsive CSS Motion Path? Sure We Can! appeared first on CSS-Tricks.


, , , ,

Unfortunately, clip-path: path() is Still a No-Go

I was extremely excited when I first heard that clip-path: path() was coming to Firefox. Just imagine being able to easily code a breathing box like the one below with just one HTML element and very little CSS without needing SVG or a huge list of points inside the polygon function!

Chris was excited about the initial implementation, too.

How fun would this be:

Animated gif. Shows a square breathing in and out - its waistline smoothly contracts and then expands.
Breathing box.

I decided to give it a try. I went on CodePen, dropped a <div> in the HTML panel, gave it dimensions in viewport units so that it scales nicely, added a background so that I could see it. Then I went on MDN to check out some usage examples… and my fluffy cloud of a dream began to crash!

Note that clip-path: path() only works in Firefox 63-70 with the layout.css.clip-path-path.enabled flag set to true in about:config and in Firefox 71+ without needing to enable any flag. (Source: MDN.)

These were the examples I found:

path('M0 200L0 110A110 90 0 0 1 240 100L 200 340z') path('M.5 1C.5 1 0 .7 0 .3A.25 .25 1 1 1 .5 .3 .25 .25 1 1 1 1 .3C1 .7 .5 1 .5 1Z')

What are those coordinates? The sad answer is pixel values! Those are used because the path() function takes an SVG <path> string as an argument which — like the value of the SVG d attribute on a <path> element — only contains one kind of coordinate value: unit-less pixels. In the SVG case, these pixels scale with the viewBox of the <svg> element but they don’t scale at all inside the CSS path() function!

This means the element always gets clipped to the same fixed area if we have a responsive element with a path() value for the clip-path property. For example, consider a square .box whose edge length is 35vw. We clip it to a heart using the path() function:

clip-path: path('M256 203C150 309 150 309 44 203 15 174 15 126 44 97 73 68 121 68 150 97 179 68 227 68 256 97 285 126 285 174 256 203')

This heart shape stays the same size while the dimensions of our actual .box element changes with the viewport:

Animated gif. Shows how the heart clipped using a fixed pixel path() doesn't fit within the element's bounding rectangle when its viewport-depending size goes down at small screen sizes.
The issue with a fixed pixel path().

This is bad news here in 2020, where responsive design is the standard, not the exception. Save for the odd case where the element we want to clip actually has a fixed pixel size, the path() function is completely useless! We’re still better off using an actual SVG today, or even a polygon() approximation value for clip-path. In short, path() is still in need of improvement, despite getting off the ground.

Amelia Bellamy-Royds has suggested two possibilities here:

Option 1: Allow calc() values/units inside path data. This would probably be done while extending SVG path syntax in general.

Option 2: Specify viewBox in clip-path declaration, scale path to fit.

I personally prefer the first option. The only advantage the second one offers over using SVG is the fact that we don’t have to include an actual SVG. That said, including an actual SVG is always going to have better support.

The first option, however, could be a huge improvement over using SVG — at least enough of an improvement to justify using clip-path on an HTML element instead of including an SVG inside it. Let’s consider the breathing box at the top of this post. Using SVG, we have the following markup:

<svg viewBox='-75 -50 150 100'>   <path/> </svg>

Note that the viewBox is set such that the 0,0 point is dead in the middle. This means we’ve got to make the coordinates of the top-left corner (i.e. first two viewBox values) equal to minus half the viewBox dimensions (i.e. the last two viewBox values).

In SCSS, we set the edge length ($ l) of the initial square box as the smallest viewBox dimension (which is the smallest of the last two values). This is 100 in our case.

We start the path from the top-left corner of our square box. This means a move to (M) command to this point, with coordinates that are both equal to minus half the length of the edge.

We then go down to the bottom-left corner. This requires drawing a vertical line with a length that equals an edge length ($ l) and goes down, in the positive direction of the y axis. So, we’ll use the v command.

Next, we go to the bottom-right corner. We’ll draw a horizontal line with a length that equals an edge length ($ l) and goes right, in the positive direction of the x axis. We’ll use the h command to make that happen.

Going to the top-right corner means drawing another vertical line of with a length equal to the edge length ($ l), so we will use the v command again — only this time, the difference is the fact that we go in the opposite direction of the y axis, meaning we use the same coordinates, but with a minus sign.

Putting it all together, we have the SCSS that allows us to create the initial square box:

.box {   d: path('M#{-.5*$ l},#{-.5*$ l} v#{$ l} h#{$ l} v#{-$ l}');   fill: darkorange }

The generated CSS (where $ l is replaced with 100) looks like this:

.box {   d: path('M-50,-50 v100 h100 v-100');   fill: darkorange; }

The result can be seen in the interactive demo below where hovering a part of the path data highlights the corresponding part in the resulting SVG and the other way around:

See the Pen by thebabydino (@thebabydino) on CodePen.

However, if we want the lateral edges to breathe, we can’t use straight lines. Let’s replace those with quadratic Bézier (q) ones. The end point remains the same, which is one edge length down along the same vertical line. We travel by 0,#{$ l} to get there.

But what about the control point we need to specify before that? We place the point to be vertically midway between the start and end points, meaning we go down to it by half of we travel to get to the end point.

And let’s say that, horizontally, we place it by a quarter of an edge length to the side in one direction or the other. If we want the lines to protrude to widen the box or squeeze them in to narrow it, we need to do something like this:

d: path('M#{-.5*$ l},#{-.5*$ l}           q#{-.25*$ l},#{.5*$ l} 0,#{$ l}           h#{$ l}           v#{-$ l}'); /* swollen box */  d: path('M#{-.5*$ l},#{-.5*$ l}           q#{.25*$ l},#{.5*$ l} 0,#{$ l}           h#{$ l}           v#{-$ l}'); /* squished box */

This compiles to the following CSS:

d: path('M-50,-50           q-25,50 0,100           h100           v-100'); /* swollen box */  d: path('M-50,-50           q25,50 0,100           h100           v-100'); /* squished box */

The interactive demo below shows how this path works. You can hover over path data components to see them highlighted on the SVG graphic. You can also toggle between the swollen and squished versions.

See the Pen by thebabydino (@thebabydino) on CodePen.

This is only the left edge. We need to do the same thing for the right edge as well. The difference here is that we’re going from the bottom-right corner to the top-right corner instead, which is up (in the negative direction of the y axis). We’ll place the control point outside the box to get the wide ox effect, which also means placing it to the right of its endpoints (in the positive direction of the x axis). Meanwhile, we’ll place the control point inside to get the narrow box effect, which means placing it to the left of its endpoints (in the negative direction of the x axis).

d: path('M#{-.5*$ l},#{-.5*$ l}           q#{-.25*$ l},#{.5*$ l} 0,#{$ l}           h#{$ l}           q#{.25*$ l},#{-.5*$ l} 0,#{-$ l}'); /* swollen box */  d: path('M#{-.5*$ l},#{-.5*$ l}           q#{.25*$ l},#{.5*$ l} 0,#{$ l}           h#{$ l}           q#{-.25*$ l},#{-.5*$ l} 0,#{-$ l}'); /* squished box */

The above SCSS generates the CSS below:

d: path('M-50,-50           q-25,50 0,100           h100           q25,-50 0,100'); /* swollen box */  d: path('M-50,-50           q25,50 0,100           h100           q-25,-50 0,-100'); /* squished box */

See the Pen by thebabydino (@thebabydino) on CodePen.

In order to get the breathing effect, we animate between the swollen state and the squished state:

.box {   d: path('M#{-.5*$ l},#{-.5*$ l}             q#{-.25*$ l},#{.5*$ l} 0,#{$ l}             h#{$ l}             q#{.25*$ l},#{-.5*$ l} 0,#{-$ l}'); /* swollen box */   animation: breathe .5s ease-in-out infinite alternate }  @keyframes breathe {   to {     d: path('M#{-.5*$ l},#{-.5*$ l}               q#{.25*$ l},#{.5*$ l} 0,#{$ l}               h#{$ l}               q#{-.25*$ l},#{-.5*$ l} 0,#{-$ l}'); /* squished box */   } }

Since the only thing that differs between the two states is the sign of the horizontal difference to the control points (the sign of the first number after the quadratic Bézier curve q command), we can simplify things with a mixin:

@mixin pdata($ s: 1) {   d: path('M#{-.5*$ l},#{-.5*$ l}             q#{-.25*$ s*$ l},#{.5*$ l} 0,#{$ l}             h#{$ l}             q#{.25*$ s*$ l},#{-.5*$ l} 0,#{-$ l}') }  .box {   @include pdata();   animation: breathe .5s ease-in-out infinite alternate }  @keyframes breathe { to { @include pdata(-1) } }

This is pretty much what I’m doing for the actual breathing box demo, though the motion is slightly more discreet. Still, this does absolutely nothing for the generated CSS — we still have two long, ugly and almost identical paths in the compiled code.

However, if we were able to use a <div>, clipped with a clip-path: path() that supported all sorts of values, including calc() values inside, then we could make the sign a custom property --sgn, which we could then animate between -1 and 1 with the help of Houdini.

div.box {   width: 40vmin; height: 20vmin;   background: darkorange;   --sgn: 1;   clip-path: path(M 25%,0%                   q calc(var(--sgn)*-25%),50% 0,100%                   h 50%                   q calc(var(--sgn)*25%),-50% 0,-100%);   animation: breathe .5s ease-in-out infinite alternate }  @keyframes breathe { to { --sgn: -1 } }

Being able to do this would make a whole world of difference. Our element would scale nicely with the viewport and so would the breathing box we clip out of it. And, most importantly, we wouldn’t need to repeat this clipping path in order to get the two different versions of it (the swollen one and the squished one), because using the sign custom property (--sgn) inside a calc() value would do the trick. As it is right now, however, clip-path: path() is pretty much useless.

The post Unfortunately, clip-path: path() is Still a No-Go appeared first on CSS-Tricks.


, , , ,

Animate SVG Path Changes in CSS

Every once in a while I’m motivated to attempt to draw some shapes with <path>, the all-powerful drawing syntax of SVG. I only understand a fragment of what it all can do, but I know enough to be dangerous. All the straight-line syntax commands (like L) are pretty straightforward and I find the curved Q command fairly intuitive. Box yourself into a viewBox="0 0 100 100" and drawing simple stuff doesn’t seem so bad.

Here’s a classic example of mine that draws things with all the basic commands, but also animates them with CSS (Chromium browsers only):

Weird but true:

<svg viewBox="0 0 10 10">   <path d="M2,2 L8,8" /> </svg>
svg:hover path {   transition: 0.2s;   d: path("M8,2 L2,8"); }

The other day I had a situation where I needed a UI element that has a different icon depending on what state it’s in. It was kind of a “log” shape so the default was straight lines, kinda like a hamburger menu (only four lines so it read more like lines of text), then other various states.

  4. ERROR

First I wrote the most complicated state machine in the world:

const indicator = document.querySelector(".element");  let currentState = indicator.dataset.state;  indicator.addEventListener("click", () => {   let nextState = "";    if (currentState == "DEFAULT") {     nextState = "ACTIVE";   } else if (currentState == "ACTIVE") {     nextState = "SUCCESS";   } else if (currentState == "SUCCESS") {     nextState = "ERROR";   } else {     nextState = "DEFAULT";   }      indicator.dataset.state = nextState;   currentState = nextState; });

That opened the door for styling states with data-attributes:

.element {      &[data-state="DEFAULT"] {   }   &[data-state="ACTIVE"] {   }   &[data-state="SUCCESS"] {   }   &[data-state="ERROR"] {   }  }

So now if my element starts with the default state of four lines:

<div class="element" data-state="DEFAULT">   <svg viewBox="0 0 100 100" class="icon">     <path d="M0, 20 Q50, 20 100, 20"></path>     <path d="M0, 40 Q50, 40 100, 40"></path>     <path d="M0, 60 Q50, 60 100, 60"></path>     <path d="M0, 80 Q50, 80 100, 80"></path>   </svg> </div> 

…I can alter those paths in CSS for the rest of the states. For example, I can take those four straight lines and alter them in CSS.

Note the four “straight” lines conveniently have an unused curve point in them. Only paths that have the same number and type of points in them can be animated in CSS. Putting the curve point in there opens doors.

These four new paths actually draw something close to a circle!

.editor-indicator {      &[data-state="ACTIVE"] {     .icon {       :nth-child(1) {         d: path("M50, 0 Q95, 5 100,50");       }       :nth-child(2) {         d: path("M100, 50 Q95, 95 50,100");       }       :nth-child(3) {         d: path("M50,100 Q5, 95 0, 50");       }       :nth-child(4) {         d: path("M0, 50 Q5, 5 50, 0");       }     }   }  }

For the other states, I drew a crude checkmark (for SUCCESS) and a crude exclamation point (for FAILURE).

Here’s a demo (again, Chromium), where you can click it to change the states:

I didn’t end up using the thing because neither Firefox nor Safari support the d: path(); thing in CSS. Not that it doesn’t animate them, it just doesn’t work period, so it was out for me. I just ended up swapping out the icons in the different states.

If you need cross-browser shape morphing, we have a whole article about that.

The post Animate SVG Path Changes in CSS appeared first on CSS-Tricks.


, ,

Get Moving (or not) with CSS Motion Path

We just linked up the idea that offset-path can be cleverly used to set type on a path. Don’t miss Michelle Barker’s experimentation either, with drawing paths or animating text along a path.

Dan Wilson has also been following this tech for quite a while and points out why the sudden surge of interest in this:

With the release of Firefox 72 on January 7, 2020, CSS Motion Path is now in Firefox, new Edge (slated for a January 15, 2020 stable release), Chrome, and Opera (and other Blink-based browsers). That means each of these browsers supports a common baseline of offset-path: path()offset-distance, and offset-rotate.

Dan’s post does a great job of covering the basics, including some things you might not think of, like the fact that the path itself can be animated.

Direct Link to ArticlePermalink

The post Get Moving (or not) with CSS Motion Path appeared first on CSS-Tricks.


, ,

Movin’ Modals Along a Path

Modals always be just appearin’. You might see one once in a while that slides in from one of the edges, or uses some kind of scale/opacity thing to appear from “above” or “below.” But we can get weirder than that. Why not have them come in on an offset-path?

Just a swoopy arc is kinda fun.

See the Pen
Move Modal In on Path
by Chris Coyier (@chriscoyier)
on CodePen.

Or we could Mary Poppins it and have it come floating in from afar.

See the Pen
Move Modal In on Path: Mary Poppins Edition
by Chris Coyier (@chriscoyier)
on CodePen.

Or get straight up wiggly woggly.

See the Pen
Move Modal In on Path: Wackadoo
by Chris Coyier (@chriscoyier)
on CodePen.

That’s all. I figured you were here for the CSS tricks, anyway. 😉

The post Movin’ Modals Along a Path appeared first on CSS-Tricks.


, , ,

8 Little Videos About the Firefox Shape Path Editor

It sometimes takes a quick 35 seconds for a concept to really sink in. Mikael Ainalem delivers that here, in the case that you haven’t quite grokked the concepts behind path-based CSS properties like clip-path and shape-outside.

Here are two of my favorites. The first demonstrates animating text into view using a polygon as a clip.

The second shows how the editor can help morph one shape into another.

Direct Link to ArticlePermalink

The post 8 Little Videos About the Firefox Shape Path Editor appeared first on CSS-Tricks.


, , , , , ,

An Initial Implementation of clip-path: path();

One thing that has long surprised (and saddened) me is that the clip-path property, as awesome as it is, only takes a few values. The circle() and ellipse() functions are nice, but hiding overflows and rounding with border-radius generally helps there already. Perhaps the most useful value is polygon() because it allows us to draw a shape out of straight lines at arbitrary points.

Here’s a demo of each value:

See the Pen clip-path examples by Chris Coyier (@chriscoyier) on CodePen.

The sad part comes in when you find out that clip-path doesn’t accept path(). C’mon it’s got path in the name! The path syntax, which comes from SVG, is the ultimate syntax. It allows us to draw literally any shape.

More confusingly, there already is a path() function, which is what properties like offset-path take.

I was once so flabbergasted by all this that I turned it into a full conference talk.

The talk goes into the shape-outside property and how it can’t use path(). It also goes into the fact that we can change the d property of a literal <path>.

I don’t really blame anyone, though. This is weird stuff and it’s being implemented by different teams, which inevitably results in different outcomes. Even the fact that SVG uses unit-less values in the <path> syntax is a little weird and an anomaly in CSS-land. How that behaves, how values with units behave, what comma-syntax is allowed and disallowed, and what the DOM returns when asked is plenty to make your head spin.

Anyway! Along comes Firefox with an implementation!

Here’s that flag in Firefox (layout.css.clip-path-path.enabled):

And here’s a demo… you’ll see a square in unsupported browsers and a heart in the ones that support clip-path: path(); — which is only Firefox Nightly with the flag turned on at the time of this writing.

See the Pen clip-path: path()! by Chris Coyier (@chriscoyier) on CodePen.

A screenshot of clip-path: path() working in Firefox Nightly

Now, all we need is:

  • clip-path to be able to point to the URL of a <clipPath> in SVG, like url("#clip-path");
  • shape-outside to be able to use path()
  • shape-outside to be able to use a <clipPath>
  • offset-path to take all the other shape functions
  • Probably a bunch of specs to make sure this is all handled cleanly (Good luck, team!)
  • Browsers to implement it all


The post An Initial Implementation of clip-path: path(); appeared first on CSS-Tricks.


, , ,