Read Time: 9 min

How to Code a CSS Glitch Effect in Email [+ Code]

Categories

The Halloween edition of newsletter took a seasonally spooky turn. Our email team created a fabulously freaky email using glitching animations, all created using CSS.

 

Read to find out how I created this CSS glitch effect.

Image of email icon

Subscribe for more email tricks and treats

Keep your know-how fresh on email marketing, design, and development with our newsletters—including “surprise and delight” moments like our October newsletter.

 

Tutorial: CSS glitch effect in email

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. 

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. Let’s 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;" /></a>
</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 for the image:

<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;" /></a>
 	</div>
</div>

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

<h2><span class="glitch-text" data-text="How to Create Clickable Phone Numbers with HTML in Emails">How to Create Clickable Phone Numbers with HTML in Emails</span></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 aria-label="How to Create Clickable Phone Numbers with HTML in Emails"><span class="glitch-text" data-text="How to Create Clickable Phone Numbers with HTML in Emails" aria-hidden="true"><a rel="noopener" target="_blank" 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></span></h2>

I used the screen reader with Litmus Email Testing 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 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 CSS 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. 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. Since the rectangles are directly on top of the content, I moved the content to the left a bit during the animation. 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? I wanted the rectangle to move pretty fast to create a flicker, so I knew 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%, and stays static after that. The rectangles are 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 okay 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, as the longer duration looked better on the text. Here is 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 not showing up correctly in some email clients in Litmus, so I added some targeting to take it out of those specific clients. That’s something you’ll have to do on a case by case basis when you start testing it in your email.

Ensure your designs come across right

Broken emails lead to less conversions. Preview your emails across 100+ email clients, apps, and devices to ensure an on-brand, error-free subscriber experience. Every time.

Carin Slater

Carin Slater

Carin Slater is the Email and Content Growth Marketing Manager at Litmus