<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>
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;
}

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( [
			"hello friend!",
			"HELLO FRIEND?",
			"µ37(0 [R132q?",
			"µ31)* {&13Nb?",
			"#+:|* {&><@$?",
		] );
		
		// 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();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.