blog-image
  • Published Date: 2024-07-23
  • Last Modified: 2024-09-11

Cyberpunk - Style Glitch Text Walkthrough

This came out of a 24-hour work sprint to replicate some sophisticated video VFX and to make it more flexible.

There’s a lot of pieces that go into an effect like this. What follows is an outline of the animations and VFX concepts, all in pure css :)

Core principles:

  • Keep the text legible most of the time
  • Don’t throw too many glitches in per second. Give it space to breathe
  • Whitespace never hurt anyone
  • Blurs & glows lend a feeling of realism in dark environments (queue neon)


The Effect

This is how the complete effect looks like

The Effect (What we are going to make)

近設計

Layers

Create multiple layers to play with. For this example I’ll use ::before & ::after elements, but this could just as easily be multiple div’s overlapped, or whatever.

EXAMPLE

Example layers CSS:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.layers {
    position: relative;
}

.layers::before,
.layers::after {
    content: attr(data-text);
    position: absolute;
    width: 110%;
    z-index: -1;
}

.layers::before {
    top: 10px;
    left: 15px;
    color: #e0287d;
}

.layers::after {
    top: 5px;
    left: -10px;
    color: #1bc7fb;
}

Paths

Build out a set of clip-paths (I wrote up a generator util for this project)

Example Path:

Over Text:

EXAMPLE

Example paths CSS:
  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
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
.single-path {
    clip-path: polygon(
        0% 12%,
        53% 12%,
        53% 26%,
        25% 26%,
        25% 86%,
        31% 86%,
        31% 0%,
        53% 0%,
        53% 84%,
        92% 84%,
        92% 82%,
        70% 82%,
        70% 29%,
        78% 29%,
        78% 65%,
        69% 65%,
        69% 66%,
        77% 66%,
        77% 45%,
        85% 45%,
        85% 26%,
        97% 26%,
        97% 28%,
        84% 28%,
        84% 34%,
        54% 34%,
        54% 89%,
        30% 89%,
        30% 58%,
        83% 58%,
        83% 5%,
        68% 5%,
        68% 36%,
        62% 36%,
        62% 1%,
        12% 1%,
        12% 34%,
        60% 34%,
        60% 57%,
        98% 57%,
        98% 83%,
        1% 83%,
        1% 53%,
        91% 53%,
        91% 84%,
        8% 84%,
        8% 83%,
        4% 83%
    );
}

.paths {
    animation: paths 5s step-end infinite;
}

@keyframes paths {
    0% {
        clip-path: polygon(
            0% 43%,
            83% 43%,
            83% 22%,
            23% 22%,
            23% 24%,
            91% 24%,
            91% 26%,
            18% 26%,
            18% 83%,
            29% 83%,
            29% 17%,
            41% 17%,
            41% 39%,
            18% 39%,
            18% 82%,
            54% 82%,
            54% 88%,
            19% 88%,
            19% 4%,
            39% 4%,
            39% 14%,
            76% 14%,
            76% 52%,
            23% 52%,
            23% 35%,
            19% 35%,
            19% 8%,
            36% 8%,
            36% 31%,
            73% 31%,
            73% 16%,
            1% 16%,
            1% 56%,
            50% 56%,
            50% 8%
        );
    }

    5% {
        clip-path: polygon(
            0% 29%,
            44% 29%,
            44% 83%,
            94% 83%,
            94% 56%,
            11% 56%,
            11% 64%,
            94% 64%,
            94% 70%,
            88% 70%,
            88% 32%,
            18% 32%,
            18% 96%,
            10% 96%,
            10% 62%,
            9% 62%,
            9% 84%,
            68% 84%,
            68% 50%,
            52% 50%,
            52% 55%,
            35% 55%,
            35% 87%,
            25% 87%,
            25% 39%,
            15% 39%,
            15% 88%,
            52% 88%
        );
    }

    30% {
        clip-path: polygon(
            0% 53%,
            93% 53%,
            93% 62%,
            68% 62%,
            68% 37%,
            97% 37%,
            97% 89%,
            13% 89%,
            13% 45%,
            51% 45%,
            51% 88%,
            17% 88%,
            17% 54%,
            81% 54%,
            81% 75%,
            79% 75%,
            79% 76%,
            38% 76%,
            38% 28%,
            61% 28%,
            61% 12%,
            55% 12%,
            55% 62%,
            68% 62%,
            68% 51%,
            0% 51%,
            0% 92%,
            63% 92%,
            63% 4%,
            65% 4%
        );
    }

    45% {
        clip-path: polygon(
            0% 33%,
            2% 33%,
            2% 69%,
            58% 69%,
            58% 94%,
            55% 94%,
            55% 25%,
            33% 25%,
            33% 85%,
            16% 85%,
            16% 19%,
            5% 19%,
            5% 20%,
            79% 20%,
            79% 96%,
            93% 96%,
            93% 50%,
            5% 50%,
            5% 74%,
            55% 74%,
            55% 57%,
            96% 57%,
            96% 59%,
            87% 59%,
            87% 65%,
            82% 65%,
            82% 39%,
            63% 39%,
            63% 92%,
            4% 92%,
            4% 36%,
            24% 36%,
            24% 70%,
            1% 70%,
            1% 43%,
            15% 43%,
            15% 28%,
            23% 28%,
            23% 71%,
            90% 71%,
            90% 86%,
            97% 86%,
            97% 1%,
            60% 1%,
            60% 67%,
            71% 67%,
            71% 91%,
            17% 91%,
            17% 14%,
            39% 14%,
            39% 30%,
            58% 30%,
            58% 11%,
            52% 11%,
            52% 83%,
            68% 83%
        );
    }

    76% {
        clip-path: polygon(
            0% 26%,
            15% 26%,
            15% 73%,
            72% 73%,
            72% 70%,
            77% 70%,
            77% 75%,
            8% 75%,
            8% 42%,
            4% 42%,
            4% 61%,
            17% 61%,
            17% 12%,
            26% 12%,
            26% 63%,
            73% 63%,
            73% 43%,
            90% 43%,
            90% 67%,
            50% 67%,
            50% 41%,
            42% 41%,
            42% 46%,
            50% 46%,
            50% 84%,
            96% 84%,
            96% 78%,
            49% 78%,
            49% 25%,
            63% 25%,
            63% 14%
        );
    }

    90% {
        clip-path: polygon(
            0% 41%,
            13% 41%,
            13% 6%,
            87% 6%,
            87% 93%,
            10% 93%,
            10% 13%,
            89% 13%,
            89% 6%,
            3% 6%,
            3% 8%,
            16% 8%,
            16% 79%,
            0% 79%,
            0% 99%,
            92% 99%,
            92% 90%,
            5% 90%,
            5% 60%,
            0% 60%,
            0% 48%,
            89% 48%,
            89% 13%,
            80% 13%,
            80% 43%,
            95% 43%,
            95% 19%,
            80% 19%,
            80% 85%,
            38% 85%,
            38% 62%
        );
    }

    1%,
    7%,
    33%,
    47%,
    78%,
    93% {
        clip-path: none;
    }
}

Movement

Create a stepping movement animation, so that the layers above can appear in different places as the other layers of the animation happen.

It’s best to make this timing somewhat erratic.

Example movement CSS:
 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
.movement {
    /* Normally this position would be
    absolute & on the layers, set to relative
    here so we can see it on the div */
    position: relative;
    animation: movement 8s step-end infinite;
}

@keyframes movement {
    0% {
        top: 0px;
        left: -20px;
    }

    15% {
        top: 10px;
        left: 10px;
    }

    60% {
        top: 5px;
        left: -10px;
    }

    75% {
        top: -5px;
        left: 20px;
    }

    100% {
        top: 10px;
        left: 5px;
    }
}

Opacity

Create a similar animation for opacity, so that the layers can appear/disappear. This will get layered with the movement to create the appearance of the layers popping in and out in different places.

There’s an art to getting this transition to work without making the user feel motion sickness. Take some time getting it right!

Example opacity CSS:
 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
.opacity {
    animation: opacity 5s step-end infinite;
}

@keyframes opacity {
    0% {
        opacity: 0.1;
    }

    5% {
        opacity: 0.7;
    }

    30% {
        opacity: 0.4;
    }

    45% {
        opacity: 0.6;
    }

    76% {
        opacity: 0.4;
    }

    90% {
        opacity: 0.8;
    }

    1%,
    7%,
    33%,
    47%,
    78%,
    93% {
        opacity: 0;
    }
}

Font

The next layer is changes to the font directly. These could be any properties, but we’ll stick to font-weight and color.

EXAMPLE

Example font CSS:
 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
.font {
    animation: font 7s step-end infinite;
}

@keyframes font {
    0% {
        font-weight: 100;
        color: #e0287d;
        filter: blur(3px);
    }

    20% {
        font-weight: 500;
        color: #fff;
        filter: blur(0);
    }

    50% {
        font-weight: 300;
        color: #1bc7fb;
        filter: blur(2px);
    }

    60% {
        font-weight: 700;
        color: #fff;
        filter: blur(0);
    }

    90% {
        font-weight: 500;
        color: #e0287d;
        filter: blur(6px);
    }
}

Combined Animation Layers

Note that some of the animations have sync’d timing, and some don’t.

I try to sync the paths & opacity animations, so that it looks like the background layers are really a part of the main text that’s glitching off.

Then I try to separately sync the color/text/filter other effects, and keep the movement separate completely. This gives the whole thing the appearance of a lot more effects than are actually happening, because of the different combinations.

EXAMPLE

Example CSS:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
.glitch span {
    animation: paths 5s step-end infinite;
}

.glitch::before {
    animation: paths 5s step-end infinite, opacity 5s step-end infinite,
        font 8s step-end infinite, movement 10s step-end infinite;
}

.glitch::after {
    animation: paths 5s step-end infinite, opacity 5s step-end infinite,
        font 7s step-end infinite, movement 8s step-end infinite;
}

Environment

For good measure, it should live in the right language & context :)

近設計

background image from: unsplash.com
Example environment CSS:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
.hero-container {
    position: relative;
    padding: 200px 0;
    text-align: center;
}

.environment {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0.5;
    filter: blur(5px);
    background: url(https://images.unsplash.com/photo-1602136773736-34d445b989cb?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1200&q=80)
        center no-repeat;
    background-size: cover;
}
Example HTML layout:
1
2
3
4
<div class="hero-container">
    <div class="environment"></div>
    <h2 class="hero glitch layers" data-text="近設計"><span>近設計</span></h2>
</div>

Conclusion

With this walkthrough, you should now have a basic understanding of how to create glitch effects using pure CSS. By layering text, applying clip-paths, and animating movement, opacity, and font properties, you can achieve a striking cyberpunk-style effect. Experiment with these techniques to customize and enhance your web projects with a unique, futuristic flair. Dive in and start glitching!