CSS only fragmentation effect using one element
Tags: css, webdev, tutorial
Published: 2021-03-23 21:51:23

<sup>**hold on**. The below is not supported on Firefox.</sup>

Here's a working demo of the result. No JavaScript handling, no SVG trickery. Only a single `<img>` and some CSS. Hover to see the magic.

{% codepen https://codepen.io/t_afif/pen/WNoWBmp %}

Cool, right? well, I have to admit it's not only CSS since I relied on SCSS but still it's a pure CSS effect with no JS involved 😉.

The above rely on 2 things. 

1. Masking things
2. Animating gradients

----

## Masking things

Masking is sometimes hard to conceptualize and often gets confused with clipping. The bottom line: masks are images. When an image is applied as mask to an element, any transparent parts of the image allow us see right through the element. Any opaque parts will make the element fully visible.

Masks work the same way as opacity, but on different portions of the same element. That's different from clipping, which is a path where everything outside the path is simply hidden. The advantages of masking is that we can have as many mask layers as we want on the same element — similar to how we can chain multiple images on `background-image`.

And since masks are images, we get to use CSS gradients to make them. Let’s take an easy example to better understand the trick.

```css
img {
  mask:
    linear-gradient(rgba(0,0,0,0.8) 0 0) left,  /* 1 */
    linear-gradient(rgba(0,0,0,0.5) 0 0) right; /* 2 */
  mask-size: 50% 100%;
  mask-repeat: no-repeat;
}
```

![mask layers](https://i2.wp.com/css-tricks.com/wp-content/uploads/2021/03/gjlgtv44jz5m7nta8s17.png?w=417&ssl=1)

Here, we're defining two mask layers on an image. They are both a solid color but the alpha transparency values are different.

It's worth noting that the color we use is irrelevant since the default `mask-mode` is `alpha`. The alpha value is the only relevant thing. Our gradient can be `linear-gradient(rgba(X,Y,Z,0.8) 0 0)` where `X`, `Y` and `Z` are random values.

Each mask layer is equal to `50% 100%` (or half width and full height of the image). One mask covers the left and the other covers the right. At the end, we have two non-overlapping masks covering the whole area of the image and, as we discussed earlier, each one has a differently defined alpha transparency value.

##Animating gradients

What we want to do is apply an animation to the linear gradient alpha values of our mask to create a transparency animation. Later on, we'll make these into asynchronous animations that will create the fragmentation effect.

Animating gradients is something we've been unable to do in CSS. That is, until we got limited support for `@property`.

Refer to my previous post for more detail

{% post https://dev.to/afif/we-can-finally-animate-css-gradient-kdk %}

In short, `@property` lets us create custom CSS properties where we're able to define the syntax by specifying a type. Let's create two properties, `--c-0` and `--c-1` , that take a number with an initial value of `1`.

```css
@property --c-0 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
@property --c-1 {
   syntax: "<number>";
   initial-value: 1;
   inherits: false;
}
```

Those properties are going to represent the alpha values in our CSS mask. And since they both default to fully opaque (i.e. `1` ), the entire image shows through the mask. Here’s how we can rewrite the mask using the custom properties:

```css
/* Omitting the @property blocks above for brevity */

img {
  mask:
    linear-gradient(rgba(0,0,0,var(--c-0)) 0 0) left,  /* 1 */
    linear-gradient(rgba(0,0,0,var(--c-1)) 0 0) right; /* 2 */
  mask-size: 50% 100%;
  mask-repeat: no-repeat;
  transition: --c-0 0.5s, --c-1 0.3s 0.4s;
}

img:hover {
  --c-0:0;
  --c-1:0;
}
```

All we’re doing here is applying a different transition duration and delay for each custom variable. Go ahead and hover the image. The first gradient of the mask will fade out to an alpha value of `0` to make the image totally see through, followed but the second gradient.

{% codepen https://codepen.io/t_afif/pen/YzpMoPz %}

## More masking!

So far, we've only been working with two linear gradients on our mask and two custom properties. To create the fragmentation effect, we'll need lots more tiles, and that means lots more gradients and a lot of custom properties!

SCSS makes this a fairly trivial task, so that's what we're turning to for writing styles from here on out. As we saw in the first example, we have a kind of matrix of tiles. We can think of those as rows and columns, so let's define two SCSS variables, `$x` and `$y` to represent them.

#### Custom properties

We're going to need `@property` definitions for each one. No one wants to write all those out by hand, though, so let's allow SCSS do the heavy lifting for us by running our properties through a loop:

```scss
@for $i from 0 through ($x - 1) {
  @for $j from 0 through ($y - 1) {
    @property --c-#{$i}-#{$j} {
      syntax: "<number>";
      initial-value: 1;
      inherits: false;
    }
  }
}
```

Then we make all of them go to `0` on hover:

```scss
img:hover {
  @for $i from 0 through ($x - 1) {
    @for $j from 0 through ($y - 1) {
      --c-#{$i}-#{$j}: 0;
    }
  }
}
```

#### Gradients
We're going to write a `@mixin` that generates them for us:

```scss
@mixin image() {
  $all_t: (); // Transition
  $all_m: (); // Mask
  @for $i from 0 through ($x - 1) {
    @for $j from 0 through ($y - 1) {
      $all_t: append($all_t, --c-#{$i}-#{$j} transition($i,$j), comma);
      $all_m: append($all_m, linear-gradient(rgba(0,0,0,var(--c-#{$i}-#{$j})) 0 0) calc(#{$i}*100%/(#{$x} - 1)) calc(#{$j}*100%/(#{$y} - 1)), comma);
    }
  }
  transition: $all_t;
  mask: $all_m;
}
```

All our mask layers equally-sized, so we only need one property for this, relying on the `$x` and `$y` variables and `calc()`:

```scss
mask-size: calc(100%/#{$x}) calc(100%/#{$y})
```
You may have noticed this line as well:

```scss
$all_t: append($all_t, --c-#{$i}-#{$j} transition($i,$j), comma);
```
Within the same mixing, we’re also generating the transition property that contains all the previously defined custom properties.

Finally, we generate a different duration/delay for each property, thanks to the `random()` function in SCSS.

```scss
@function transition($i,$j) {
  @return $s*random()+s $s*random()+s;
}
```

Now all we have to do is to adjust the `$x` and `$y` variables to control the granularity of our fragmentation.

----

We can extend our code and change the random configuration to consider different kind of animations.

{% codepen https://codepen.io/t_afif/pen/vYyMwqY %}

In the code above, I defined the `transition()` function like below:

```scss
// Uncomment one to use it
@function transition($i,$j) {
  // @return (($s*($i+$j))/($x+$y))+s (($s*($i+$j))/($x+$y))+s; /* diagonal */
  // @return (($s*$i)/$x)+s (($s*$j)/$y)+s; /* left to right */
  // @return (($s*$j)/$y)+s (($s*$i)/$x)+s; /* top to bottom */
  // @return  ($s*random())+s (($s*$j)/$y)+s; /* top to bottom random */
  @return  ($s*random())+s (($s*$i)/$y)+s; /* left to right random */
  // @return  ($s*random())+s (($s*($i+$j))/($x+$y))+s; /* diagonal random */
  // @return ($s*random())+s ($s*random())+s; /* full random*/
}
```

By adjusting the formula, we can get different kinds of animation. Simply uncomment the one you want to use. This list is non-exhaustive — we can have any combination by considering more forumlas. (I'll let you imagine what's possible if we add advanced math functions, like `sin()`, `sqrt()`, etc.)

We can still play around with our code by adjusting the gradient so that, instead of animating the alpha value, we animate the color stops. Our gradient will look like this:

```
linear-gradient(white var(--c-#{$i}-#{$j}),transparent 0)
```

Then we animate the variable from `100%` to `0%`. And, hey, we don't have to stick with linear gradients. Why not radial?

{% codepen https://codepen.io/t_afif/pen/ZEBZNgo %}

Like the transition, we can define any kind of gradient we want — the combinations are infinite!

Let's introduce another variable to control the overlap between our gradient masks. This variable will set the `mask-size` like this:

```scss
calc(#{$o}*100%/#{$x}) calc(#{$o}*100%/#{$y})
```

There is no overlap if it's equal to `1`. If it's bigger, then we do get an overlap. This allows us to make even more kinds of animations:

{% codepen https://codepen.io/t_afif/pen/rNWbEBr %}

## That's it!

All we have to do is to find the perfect combination between variables and formulas to create astonishing and crazy image fragmentation effects.