├── main.css ├── index.html └── app.js /main.css: -------------------------------------------------------------------------------- 1 | /* Explanation in JS tab */ 2 | 3 | /* Cool font from Google Fonts! */ 4 | @import url('https://fonts.googleapis.com/css?family=Raleway:700&display=swap'); 5 | 6 | body { 7 | margin: 0px; 8 | } 9 | 10 | #container { 11 | /* Center the text in the viewport. */ 12 | position: absolute; 13 | margin: auto; 14 | width: 100vw; 15 | height: 80pt; 16 | top: 0; 17 | bottom: 0; 18 | 19 | /* This filter is a lot of the magic, try commenting it out to see how the morphing works! */ 20 | filter: url(#threshold) blur(0.6px); 21 | } 22 | 23 | /* Your average text styling */ 24 | #text1, #text2 { 25 | position: absolute; 26 | width: 100%; 27 | display: inline-block; 28 | 29 | font-family: 'Raleway', sans-serif; 30 | font-size: 80pt; 31 | 32 | text-align: center; 33 | 34 | user-select: none; 35 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Someday... || Me 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | This pen cleverly utilizes SVG filters to create a "Morphing Text" effect. Essentially, it layers 2 text elements on top of each other, and blurs them depending on which text element should be more visible. Once the blurring is applied, both texts are fed through a threshold filter together, which produces the "gooey" effect. Check the CSS - Comment the #container rule's filter out to see how the blurring works! 3 | */ 4 | 5 | const elts = { 6 | text1: document.getElementById("text1"), 7 | text2: document.getElementById("text2") 8 | }; 9 | 10 | // The strings to morph between. You can change these to anything you want! 11 | const texts = [ 12 | "I", 13 | "Love", 14 | "You", 15 | "My", 16 | "Dream", 17 | "😇", 18 | "❤️" 19 | ]; 20 | 21 | // Controls the speed of morphing. 22 | const morphTime = 1; 23 | const cooldownTime = 0.25; 24 | 25 | let textIndex = texts.length - 1; 26 | let time = new Date(); 27 | let morph = 0; 28 | let cooldown = cooldownTime; 29 | 30 | elts.text1.textContent = texts[textIndex % texts.length]; 31 | elts.text2.textContent = texts[(textIndex + 1) % texts.length]; 32 | 33 | function doMorph() { 34 | morph -= cooldown; 35 | cooldown = 0; 36 | 37 | let fraction = morph / morphTime; 38 | 39 | if (fraction > 1) { 40 | cooldown = cooldownTime; 41 | fraction = 1; 42 | } 43 | 44 | setMorph(fraction); 45 | } 46 | 47 | // A lot of the magic happens here, this is what applies the blur filter to the text. 48 | function setMorph(fraction) { 49 | // fraction = Math.cos(fraction * Math.PI) / -2 + .5; 50 | 51 | elts.text2.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`; 52 | elts.text2.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`; 53 | 54 | fraction = 1 - fraction; 55 | elts.text1.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`; 56 | elts.text1.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`; 57 | 58 | elts.text1.textContent = texts[textIndex % texts.length]; 59 | elts.text2.textContent = texts[(textIndex + 1) % texts.length]; 60 | } 61 | 62 | function doCooldown() { 63 | morph = 0; 64 | 65 | elts.text2.style.filter = ""; 66 | elts.text2.style.opacity = "100%"; 67 | 68 | elts.text1.style.filter = ""; 69 | elts.text1.style.opacity = "0%"; 70 | } 71 | 72 | // Animation loop, which is called every frame. 73 | function animate() { 74 | requestAnimationFrame(animate); 75 | 76 | let newTime = new Date(); 77 | let shouldIncrementIndex = cooldown > 0; 78 | let dt = (newTime - time) / 1000; 79 | time = newTime; 80 | 81 | cooldown -= dt; 82 | 83 | if (cooldown <= 0) { 84 | if (shouldIncrementIndex) { 85 | textIndex++; 86 | } 87 | 88 | doMorph(); 89 | } else { 90 | doCooldown(); 91 | } 92 | } 93 | 94 | // Start the animation. 95 | animate(); --------------------------------------------------------------------------------