CSS Glitch Ghost Effect

CSS Glitch Effect
Today, we want to show you how to create a small experimental glitch effect on images, powered by CSS animations and the "clip-path" property. This technique involves using multiple image layers, each with its own clip path, blend mode, and translation applied. It is inspired by a technique seen on the speaker page of the 404 Conference.
We wanted to experiment with this and recreate the effect using the clip-path property. Although browser support is limited (it does not work in IE or Edge), it allows for more flexible usage because we can use percentage values and apply them to elements that are not necessarily absolutely positioned.
The combination of background blend modes allows us to create some interesting image effects.
This technique works by creating a stack of images, where each overlapping image has an animated clip path that appears to have a randomly changing size. We will use a stack of 5 images:
1 2 3 4 5 6 7 | <div class="glitch glitch--style-1"> <div class="glitch__img"></div> <div class="glitch__img"></div> <div class="glitch__img"></div> <div class="glitch__img"></div> <div class="glitch__img"></div></div> |
Let's take a look at the main styles for the hover effect seen in the previous demo. Note that we have already defined some variables, which should be self-explanatory:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | .glitch { position: relative; width: var(--glitch-width); max-width: 400px; height: var(--glitch-height); max-height: calc(400px * 1.25); overflow: hidden; margin: 0 auto;}.glitch:hover { cursor: pointer;}.glitch__img { position: absolute; top: calc(-1 * var(--gap-vertical)); left: calc(-1 * var(--gap-horizontal)); width: calc(100% + var(--gap-horizontal) * 2); height: calc(100% + var(--gap-vertical) * 2); background: url(../img/1.jpg) no-repeat 50% 0; background-color: var(--blend-color-1); background-size: cover; background-blend-mode: var(--blend-mode-1);} |
We don't want either side to be cut off, so we ensure that there is enough space between the image dimensions to accommodate the movement.
Next, we set the background color and blend mode for each layer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* Set the background colors for the glitch images*/.glitch__img:nth-child(2) { background-color: var(--blend-color-2); background-blend-mode: var(--blend-mode-2);}.glitch__img:nth-child(3) { background-color: var(--blend-color-3); background-blend-mode: var(--blend-mode-3);}.glitch__img:nth-child(4) { background-color: var(--blend-color-4); background-blend-mode: var(--blend-mode-4);}.glitch__img:nth-child(5) { background-color: var(--blend-color-5); background-blend-mode: var(--blend-mode-5);} |
Since this is a hover effect, we want all layers except the first one to be hidden by default:
1 2 3 | .glitch__img:nth-child(n+2) { opacity: 0;} |
Then, on hover, we display all layers:
1 2 3 | .glitch:hover .glitch__img:nth-child(n+2) { opacity: 1;} |
We also apply animations and transformations to each layer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | .glitch:hover .glitch__img:nth-child(2) { transform: translate3d(var(--gap-horizontal),0,0); animation: glitch-anim-1-horizontal var(--time-anim) infinite linear alternate;}.glitch:hover > .glitch__img:nth-child(3) { transform: translate3d(calc(-1 * var(--gap-horizontal)),0,0); animation: glitch-anim-2-horizontal var(--time-anim) infinite linear alternate;}.glitch:hover > .glitch__img:nth-child(4) { transform: translate3d(0, calc(-1 * var(--gap-vertical)), 0) scale3d(-1,-1,1); animation: glitch-anim-3-horizontal var(--time-anim) infinite linear alternate;}/* Hover flash animation on last image */.glitch:hover > .glitch__img:nth-child(5) { animation: glitch-anim-flash 0.5s steps(1,end) infinite;} |
The calc(-1 * var(--gap-horizontal)) expression basically allows us to set the negative value of a specific variable.
The last layer simply flickers and moves slightly, while the other layers are also clipped using clip paths.
Let's take a look at one of the animations used to set the clip path:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | @keyframes glitch-anim-1-horizontal { 0% { -webkit-clip-path: polygon(0 2%, 100% 2%, 100% 5%, 0 5%); clip-path: polygon(0 2%, 100% 2%, 100% 5%, 0 5%); } 10% { -webkit-clip-path: polygon(0 15%, 100% 15%, 100% 15%, 0 15%); clip-path: polygon(0 15%, 100% 15%, 100% 15%, 0 15%); } 20% { -webkit-clip-path: polygon(0 10%, 100% 10%, 100% 20%, 0 20%); clip-path: polygon(0 10%, 100% 10%, 100% 20%, 0 20%); } 30% { -webkit-clip-path: polygon(0 1%, 100% 1%, 100% 2%, 0 2%); clip-path: polygon(0 1%, 100% 1%, 100% 2%, 0 2%); } 40% { -webkit-clip-path: polygon(0 33%, 100% 33%, 100% 33%, 0 33%); clip-path: polygon(0 33%, 100% 33%, 100% 33%, 0 33%); } 50% { -webkit-clip-path: polygon(0 44%, 100% 44%, 100% 44%, 0 44%); clip-path: polygon(0 44%, 100% 44%, 100% 44%, 0 44%); } 60% { -webkit-clip-path: polygon(0 50%, 100% 50%, 100% 20%, 0 20%); clip-path: polygon(0 50%, 100% 50%, 100% 20%, 0 20%); } 70% { -webkit-clip-path: polygon(0 70%, 100% 70%, 100% 70%, 0 70%); clip-path: polygon(0 70%, 100% 70%, 100% 70%, 0 70%); } 80% { -webkit-clip-path: polygon(0 80%, 100% 80%, 100% 80%, 0 80%); clip-path: polygon(0 80%, 100% 80%, 100% 80%, 0 80%); } 90% { -webkit-clip-path: polygon(0 50%, 100% 50%, 100% 55%, 0 55%); clip-path: polygon(0 50%, 100% 50%, 100% 55%, 0 55%); } 100% { -webkit-clip-path: polygon(0 70%, 100% 70%, 100% 80%, 0 80%); clip-path: polygon(0 70%, 100% 70%, 100% 80%, 0 80%); }} |
The slices start out tiny, grow slightly larger, and remain unchanged, leaving a "pause" in some frames before starting again from another position.
A great tool for visualizing clip paths is Clippy by Bennett Feely.
The final animation is a simple flash effect on the last layer:
1 2 3 4 5 6 7 8 9 10 | @keyframes glitch-anim-flash { 0% { opacity: 0.2; transform: translate3d(var(--gap-horizontal), var(--gap-vertical), 0); } 33%, 100% { opacity: 0; transform: translate3d(0,0,0); }} |
It looks more interesting when overlapping colors are used as the blend colors.
Note that we can also apply this effect to text elements. Check out the demo to see what it does!
And that's it! We hope you find inspiration in this little experiment.