Coding a CSS Glitch

and I have no idea what I'm doing

topic - email | read time: a lot. but it's a tutorial, so bear with me if you want to learn

H'ok. This is what you've been waiting for right? Well, some of you.

So earlier this month I helped create the October Newsletter for Litmus. It was So. Much. Fun. Luckily I work with some awesome people that helped make it easy as well as fun. They also made it so much less scary to jump in to.

Making coding difficult things easier

This newsletter actually started waaaaay back in April when Jason Rodriguez essentially challenged me to create a glitch in email. I told Jaina I wanted to try and code it and asked where we could use it. She suggested the October Newsletter and then we fell down a Twilight Zone rabbit hole of ideas, which Hannah just thoroughly embraced. We started an idea board and just threw things on it as we came up with them through out the months.

Since our email team went Agile back in January (or February? I don't know it's been a long fracking year), this approach worked rather well. The October Newsletter has been in the backlog and as it got closer we started scoping out the project. I was able to add tasks for researching and figuring out how to glitch the image and text and set myself due dates so that by the time October rolled around that part of the coding was complete. It was just a matter of adding it to the newsletter, customizing it for the actual content, and figuring out how to fit it into the overall design.

The static-y button was a last minute addition, but since it was the standard hover effect that we use in our buttons except with a background image, it was a relative easy addition. (Maybe I'll delve into that in the next newsletter.) Anyways, here's the good stuff: (This is going to get a bit technical, so if you're just here for my sparkling witticisms and inspiration, you can skip to the bottom)

Say hello to Glitchy McGlitcherson

The glitching is all CSS animation using keyframes, so it's very much something that works only in the browser window or in Apple or iOS mail. Great for progressive enhancements, not so good if your subscribers open mostly on Outlook or Gmail. Luckily our audience is full of Email Geeks, so we were pretty sure it would be appreciated.

The animation is the same for the image and for the text. In both instances, I created duplicate versions of the image/text using :before and :after pseudo classes and then animated those versions. But I'm getting ahead of myself, lets start with the image. I added the image as a background in a div so I could play around with it. Then I added the image again in a <img> tag so that it would show up anywhere that the animation wasn't supported. I also added a size and shape to the div. The CSS:

.glitch-image { max-width: 560px; min-height: 250px; width: 100%; height: auto;}.image { background: url('https://campaigns.litmus.com/_email/2022/October/2022-10-Newlsetter/202210_mod2.png') no-repeat center center; background-size: 100% 100%;}

And the html for the image:

<div class="glitch-image image"> <img src="https://campaigns.litmus.com/_email/2022/October/2022-10-Newlsetter/202210_mod2.png" width="560" height="250" alt="A spooky hotel being watched over by the Litmus Live logo in the moon." style="width: 100%; max-width: 560px; height: auto;"></div>

I added another wrapper div around that glitch image. I added an overflow:hidden to the wrapper so any glitch that went outside the div didn't show up. I wanted a TV screen type of glitch for the images, where it all happens in a designated space.

.glitch-wrapper { max-width: 560px; min-height: 250px; width: 100%; height: auto; position: relative; overflow: hidden;}

And the html:

<div class="glitch-wrapper"> <div class="glitch-image image"> <img src="https://campaigns.litmus.com/_email/2022/October/2022-10-Newlsetter/202210_mod2.png" width="560" height="250" alt="A spooky hotel being watched over by the Litmus Live logo in the moon." style="width: 100%; max-width: 560px; height: auto;"> </div></div>

For the text I added a data-text attribute on a span around the text in the html:

<h2>How to Create Clickable Phone Numbers with HTML in Emails</h2>I had to add some aria around it to make it more accessible, otherwise I'd end up with screen readers reading out the :before and :after content as well. So the final version had an aria-label on the h2 to tell the screen readers what to read and an aria-hidden on the span to hide the actual copy:

<h2><a href="https://www.litmus.com/blog/html-clickable-phone-number-in-email/?utm_content=headline">How to Create Clickable Phone Numbers with HTML in Emails</a></h2>

I used the screen reader in Litmus Checklist to test to see what it would sound like and it worked perfectly.

Ok, so now that we've got the base set up, we start building the pseudo classes. For the image we're going to have those have nothing in the content, but they'll inherit the background image:

.glitch-image:after, .glitch-image:before { content: ""; background: inherit;}

For the text, I had the data-text attribute display as the content and I made sure it was the same color as the text:

.glitch-text:before, .glitch-text:after { color: #262524; content: attr(data-text);}

At this point we have three versions of the image and three versions of the text, we need to stack them on top of each other so that we can eventually hide, show, and move around the before and after content to create the glitch effect. So we'll use absolute positioning to put the pseudo class content on top of the original. .glitch-image:after, .glitch-image:before { content: ""; background: inherit; position: absolute; top: 0; left: 0; right: 0; bottom: 0;}

For the text, I added the absolute positioning, but I also had to add some positioning to the text to make sure everything stacked correctly. And some alignment and widths to the pseudo class content to make sure it lined up with the actual text. This was the CSS that let me get that all aligned: .glitch-text:before, .glitch-text:after { color: #262524; content: attr(data-text); position: absolute; top: 0; left: 0;}.glitch-text { position: relative; display: grid; grid-template-columns: 1fr;}

Fun fact: When the text was shorter and didn't span the whole width I found I had to add text-align:center and width:100% to make sure it actually ended up on top of the content. Since I only had one headline that was like that I made a special class just for that headline:

.text-glitch-a:before, .text-glitch-a:after { text-align: center; width: 100%; }

If you find that your pseudo class content isn't getting positioned correctly, you can try adding that in.

So that gives us the two elements we'll be animating. Now to add the animations. Essentially what we're going to do is create a rectangle clip-path of the additional content that moves up and down and gets hidden/shown at different points during the animation. And since the rectangles are directly on top of the content, I also moved the content to the left a bit during the animation. So here's what the animation looks like without the background:

CSS animation glitch

I used two variations of the same animation, one for the before content and one for the after content, so the content would be doing two different things when the glitch happened. How did I land on these specific points? Well I wanted the rectangle to move pretty fast to create a flicker, so I know I wanted the animation points to be close together, but I also didn't want to overwhelm anyone so I wanted there to be a good space between glitches. So the animation only goes to 25%, then stays static after that. And the rectangles I had at several different intervals. The animation CSS ended up looking like this:

@keyframes glitch-anim { 0%, 25.1% { clip-path: polygon(0 0, 0 0, 0 0, 0 0); left: 2px; } 5% { clip-path: polygon(0 80%, 100% 80%, 100% 70%, 0 70%); left: 4px; } 9% { clip-path: polygon(0 20%, 100% 20%, 100% 30%, 0 30%); left: 7px; } 10% { clip-path: polygon(0 20%, 100% 20%, 100% 20%, 0 20%); left: 6px; } 15% { clip-path: polygon(0 40%, 100% 40%, 100% 30%; 0 30%); left: 5px; } 19% { clip-path: polygon(0 60%, 100% 60%, 100% 70%, 0 70%); left: 7px; } 20% { clip-path: polygon(0 60%, 100% 60%, 100% 50%, 0 50%); left: 4px; } 25% { clip-path: polygon(0 0, 100% 0, 100% 0, 0 0); left: 5px; }}

@keyframes glitch-anim-2 { 0%, 25.1% { clip-path: polygon(0 0, 0 0, 0 0, 0 0); left: -2px; } 3% { clip-path: polygon(0 80%, 100% 80%, 100% 90%, 0 90%); left: -7px; } 7% { clip-path: polygon(0 20%, 100% 20%, 100% 30%, 0 30%); left: -3px; } 8% { clip-path: polygon(0 20%, 100% 20%, 100% 30%, 0 30%); left: -7px; } 12% { clip-path: polygon(0 40%, 100% 40%, 100% 50%; 0 50%); left: -4px; } 16% { clip-path: polygon(0 60%, 100% 60%, 100% 70%, 0 70%); left: -6px; } 17% { clip-path: polygon(0 60%, 100% 60%, 100% 60%, 0 60%); left: -5px; } 25% { clip-path: polygon(0 0, 100% 0, 100% 0, 0 0); left: -7px; }}

Now we apply the animation to the content to bring it all together. First we assign the animations to the different content:

.glitch-text:before, .glitch-image:before { animation-name: glitch-anim;}

.glitch-text:after, .glitch-image:after { animation-name: glitch-anim-2;}

Then we applied the animation settings. In this case, I'm ok with them being the same for the before and after content so I group it all together:

.glitch-image:after, .glitch-image:before, .glitch-text:before, .glitch-text:after { animation-timing-function: linear; animation-fill-mode: forwards; animation-iteration-count: infinite;}

I made the text duration longer than the image duration. I'm not 100% sure what I was thinking of when I did that, but it made sense at the time. My guess is the longer duration looked better on the text, but 🤷. In either case, here's the last bit of CSS for the animation:

.glitch-text:before, .glitch-text:after { animation-duration: 3s; animation-delay: 0s;}.glitch-image:before, .glitch-image:after { animation-duration: 7s;}.image:after, .image:before { animation-delay: 0s;}

When I did this in the newsletter, I had the images set up with different delays so that the glitches didn't happen all at the same time. I wanted to make sure that if someone was scrolling and missed one animation, they'd still see the glitch on one of the images.

The last little bits that I added were to optimize for mobile, dark mode, and reduced motion:

@media screen and (max-width:600px) { .glitch-wrapper, .glitch-image { min-height: 140px; }}@media (prefers-reduced-motion) { .glitch:before, .glitch:after, .glitch-text:before, .glitch-text:after { animation-name: none; }}@media (prefers-color-scheme: dark) { .glitch-text:before, .glitch-text:after { color: #fdfdfd; }}[data-ogsc] .glitch-text:before, [data-ogsc] .glitch-text:after { color: #fdfdfd;}

I put everything in a CSS file and hosted it on our servers so that it would only load on Apple mail and iOS. That helped with it only showing where it was supported. I ran into some of the glitches showing up weird in some email clients in Litmus, so I added some targeting to take it out of those specific clients, but that's something you'll have to do on a case by case basis when you start testing it in your email.

Fly or flop?

This was a solid "Fly". The day the newsletter went out it was hard to concentrate on work because I kept getting alerts from different things with people saying that they loved the newsletter, so yay!

Til next month; take chances, make mistakes, get messy!Carin

Jump in-spiration

When life (or a friend) challenges you, go ahead and accept that challenge and jump in! You got this! And even if you don't, well what's the worst that can happen?

Actually nix that, I suppose if it's a physical challenge (Double Dare!) then death or extreme pain would be the worst that could happen, so maybe exercise some caution when deciding to accept a challenge (unless you're Boimler). But if it doesn't put you in danger or completely stress you out, saying yes and stretching yourself a bit will usually help you grow.

Like the newsletter? Have ideas? Wanna share a story about something you dove into recently? Reply and let me know! I'm always up for new things to try.

If you’re getting this, I’m going to assume you got the welcome email and you kind of know what you signed up for. And if you don’t, that’s OK too, because I’m not 100% sure what I’m doing either. That’s kind of the point. 🤪 

If this isn't for you, feel free to unsubscribe below.

Â