This script was written for the GridSound's splashscreen (https://gridsound.com/daw/)
A Pen by grateful-dev on CodePen.
This script was written for the GridSound's splashscreen (https://gridsound.com/daw/)
A Pen by grateful-dev on CodePen.
<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; | |
} |