Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save grateful-dev/cad815ca6c4de90a6caaca79ff15b2f1 to your computer and use it in GitHub Desktop.
Save grateful-dev/cad815ca6c4de90a6caaca79ff15b2f1 to your computer and use it in GitHub Desktop.
CSS text glitch effect (clip-path & mix-blend-mode)
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@500&family=Fira+Mono:wght@500&display=swap" rel="stylesheet">
<div class="TextGlitch" id="title">
<div class="TextGlitch-clip">
<div class="TextGlitch-word"></div>
<div class="TextGlitch-word TextGlitch-blend TextGlitch-blendA"></div>
<div class="TextGlitch-word TextGlitch-blend TextGlitch-blendB"></div>
</div>
<div class="TextGlitch-clip">
<div class="TextGlitch-word"></div>
<div class="TextGlitch-word TextGlitch-blend TextGlitch-blendA"></div>
<div class="TextGlitch-word TextGlitch-blend TextGlitch-blendB"></div>
</div>
<div class="TextGlitch-clip">
<div class="TextGlitch-word"></div>
<div class="TextGlitch-word TextGlitch-blend TextGlitch-blendA"></div>
<div class="TextGlitch-word TextGlitch-blend TextGlitch-blendB"></div>
</div>
</div>
class TextGlitch {
constructor( root ) {
this._root = root;
this._elClips = root.querySelectorAll( ".TextGlitch-clip" );
this._elWords = root.querySelectorAll( ".TextGlitch-word" );
this._frame = this._frame.bind( this );
this._unglitch = this._unglitch.bind( this );
this._frameId = null;
this._text = "";
this._textAlt = [];
Object.seal( this );
this.setTexts( [
"fck.ng",
"f#$.ng",
"FCK.NG",
"F!KING",
"$#k.ng",
] );
// this.setTexts( [
// "hello world !",
// "HELLO WORLD ?",
// "µ3770 3027q ?",
// "µ311p MQ51b ?",
// ] );
}
on() {
if ( !this._frameId ) {
this._frame();
}
}
off() {
clearTimeout( this._frameId );
this._frameId = null;
this._unglitch();
}
setTexts( [ text, ...alt ] ) {
this._text = text;
this._textAlt = alt;
}
// private:
// .....................................................................
_frame() {
this._glitch();
setTimeout( this._unglitch, 50 + Math.random() * 200 );
this._frameId = setTimeout( this._frame, 250 + Math.random() * 500 );
}
_glitch() {
this._addClipCSS();
this._textContent( this._randText() );
this._root.classList.add( "TextGlitch-blended" );
}
_unglitch() {
this._removeClipCSS();
this._textContent( this._text );
this._root.classList.remove( "TextGlitch-blended" );
}
_textContent( txt ) {
this._elWords.forEach( el => el.textContent = txt );
}
// CSS clip-path, to cut the letters like an overflow:hidden
// .....................................................................
_addClipCSS() {
const clips = this._elClips,
clip1 = this._randDouble( .1 ),
clip2 = this._randDouble( .1 );
clips[ 0 ].style.transform = `translate(${ this._randDouble( .3 ) }em, .02em)`;
clips[ 2 ].style.transform = `translate(${ this._randDouble( .3 ) }em, -.02em)`;
clips[ 0 ].style.clipPath = `inset( 0 0 ${ .6 + clip1 }em 0 )`;
clips[ 1 ].style.clipPath = `inset( ${ .4 - clip1 }em 0 ${ .3 - clip2 }em 0 )`;
clips[ 2 ].style.clipPath = `inset( ${ .7 + clip2 }em 0 0 0 )`;
}
_removeClipCSS() {
this._elClips.forEach( el => {
el.style.clipPath =
el.style.transform = "";
} );
}
// Switch some chars randomly
// .....................................................................
_randText() {
const txt = Array.from( this._text );
for ( let i = 0; i < 12; ++i ) {
const ind = this._randInt( this._text.length );
txt[ ind ] = this._textAlt[ this._randInt( this._textAlt.length ) ][ ind ];
}
return txt.join( "" );
}
// rand utils
// .....................................................................
_randDouble( d ) {
return Math.random() * d - d / 2;
}
_randInt( n ) {
return Math.random() * n | 0;
}
}
const elTitle = document.querySelector( "#title" );
const glitch = new TextGlitch( elTitle );
glitch.on();
html,
body {
height: 100%;
}
body {
margin: 0;
display: flex;
overflow: hidden;
align-items: center;
justify-content: center;
background-color: #222;
}
/* ..................................................... */
.TextGlitch {
--TextGlitch-blendSize: .08em;
--TextGlitch-blendColorA: #77f8;
--TextGlitch-blendColorB: #ff68;
position: relative;
color: #fff;
line-height: 1em;
letter-spacing: -.1ch;
font-size: 8vw;
font-family: "Fira Code", monospace;
}
.TextGlitch::after {
display: none;
content: "";
position: absolute;
left: 100%;
bottom: 0;
width: .7ch;
height: 1em;
margin-left: .35ch;
border-radius: 2px;
background-color: currentColor;
animation: cursorAnim 1s ease infinite;
}
@keyframes cursorAnim {
0% { opacity: .5; }
45% { opacity: .5; }
55% { opacity: 0; }
100% { opacity: 0; }
}
.TextGlitch-clip {
position: relative;
display: flex;
align-items: baseline;
}
.TextGlitch-clip + .TextGlitch-clip {
position: absolute;
top: 0;
}
.TextGlitch:not( .TextGlitch-blended ) .TextGlitch-clip + .TextGlitch-clip {
display: none;
}
.TextGlitch-word {
margin: 0;
white-space: nowrap;
}
.TextGlitch-blend {
position: absolute;
top: 0;
opacity: 0;
transition: .1s;
transition-property: opacity;
}
.TextGlitch-blendA {
color: var( --TextGlitch-blendColorA );
margin: calc( var( --TextGlitch-blendSize ) * -1 ) 0 0 var( --TextGlitch-blendSize );
mix-blend-mode: darken;
}
.TextGlitch-blendB {
color: var( --TextGlitch-blendColorB );
margin: var( --TextGlitch-blendSize ) 0 0 calc( var( --TextGlitch-blendSize ) * -1 );
mix-blend-mode: color-burn;
}
.TextGlitch-blended .TextGlitch-blend {
opacity: .4;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment