Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Animated Letters

Animated Letters

SVG Letters animate in (and out on backspace). You can type your own message. Using Snap SVG and Greensock GSAP.

A Pen by Steve Gardner on CodePen.

License.

<div class="container background">
<svg id="svg">
<defs>
<g id="letter-z">
<path class="letter" d="M16.3,24.5l-29.1-2.1l29.1-39.5h-29.1"/>
</g>
<g id="letter-y">
<path class="letter" d="M-11.8-14.2L5.9,1.4"/>
<path class="letter" d="M15.2-20.9L-5.3,28.3"/>
</g>
<g id="letter-x">
<path class="letter" d="M18.8-10.5l-34.2,35.4"/>
<path class="letter" d="M-11-19.5l26.7,46.4"/>
</g>
<g id="letter-w">
<path class="letter" d="M22.8-14.4L7.4,21.7L-2.4,7.6l-10.2,14.1l-6.8-34"/>
</g>
<g id="letter-v">
<path class="letter" d="M16.1-16.3l-13.5,40l-15.2-40"/>
</g>
<g id="letter-u">
<path class="letter" d="M-14.5-18.9v34.3c0,0,0.2,10.1,7.7,10.6s17.3,0,17.3,0s7.7-1.6,7.4-9.7c-0.3-8.1,0-35.2,0-35.2"/>
</g>
<g id="letter-t">
<path class="letter" d="M-17.7-17.7h38.8"/>
<path class="letter" d="M1.1,25l0.6-42.7"/>
</g>
<g id="letter-s">
<path class="letter" d="M13.8-10.3c-1.6-4.3-5.9-7.5-11.1-8c-6.5-0.6-12,3.2-13.9,7.4c-1.7,3.8-0.5,8,2,10.8c4.3,4.8,8.7,5.4,17.3,8
c5.3,1.6,7.8,5.5,6.9,10.2c-0.7,3.9-5.4,6.7-9.7,7.4c-6.5,1-14-2.8-17.3-9.7"/>
</g>
<g id="letter-r">
<path class="letter" d="M-10.8,28.6l0.7-49.2v-0.2c9.6-2.5,19.4,1.1,22.9,7.7c2.6,4.8,2,11.7-3.5,16.1C3.9,7.3-4.4,7.9-10.8,4.3"/>
<path class="letter" d="M0.1,6.6l14.2,22.2"/>
</g>
<g id="letter-q">
<path class="letter" d="M0,22c-9.6,0-16.8-10.3-16.6-19.7c0.2-9.5,8-19.2,17.3-18.9C10.5-16.2,17.5-5,16.5,4.7C15.8,12.9,9,22,0,22z
"/>
<path class="letter" d="M0.3,4.3C9.8,19.6,11.8,23.2,20,24"/>
</g>
<g id="letter-p">
<path class="letter" d="M-13.3-17.6c18,2.8,30.1,8.6,30,13.4c-0.2,4.6-11.4,9-26,11"/>
<path class="letter" d="M-10.7,25l4.6-39"/>
</g>
<g id="letter-n">
<path class="letter" d="M15.4-17.6L14.6,21L-12-7.4V25"/>
</g>
<g id="letter-m">
<path class="letter" d="M26.7,27.3L11.4-20L-1.8,2.9l-8.7-18.1L-23.3,21"/>
</g>
<g id="letter-l">
<path class="letter" d="M13.3,26.1H-9.9v-44.9"/>
</g>
<g id="letter-k">
<path class="letter" d="M-9.7-18.8l0.8,44.9"/>
<path class="letter" d="M3.8-16.4L-9.3,5.8"/>
<path class="letter" d="M13.1,19.3L-9.3,5.8"/>
</g>
<g id="letter-j">
<path class="letter" d="M4.5-13c4.7,24.3,4.7,37.4,0,39.4C1.3,27.8-4,24-11.3,15.4"/>
<path class="letter" d="M-8.1-7.9l22.9-11.4"/>
</g>
<g id="letter-i">
<path class="letter" d="M1.7-18.8v44.9"/>
</g>
<g id="letter-h">
<path class="letter" d="M-9.7-16.7l0,40.7"/>
<path class="letter" d="M13.9-16.7l0,40.7"/>
<path class="letter" d="M13.9,7.5l-24.4,0"/>
</g>
<g id="letter-g">
<path class="letter" d="M7.8,3.4l10.3,0.3v20c-9.7,5.4-21,5-28.4-0.8C-21.9,13.6-21.4-8-8.7-16.5c8.6-5.8,21.3-4.7,30.7,3.2"/>
</g>
<g id="letter-f">
<path class="letter" d="M20.9-17.2l-28.1,0.8l1.6,41"/>
<path class="letter" d="M4.7,0.9l-22.2,0.8"/>
</g>
<g id="letter-e">
<path class="letter" d="M8.4,3.6l-21.3,0.5"/>
<path class="letter" d="M16.3-18.9l-24.4,0.8l-0.8,44.5l22.9-0.2"/>
</g>
<g id="letter-d">
<path class="letter" d="M-6.6-20.1l-1.6,47.5"/>
<path class="letter" d="M-18.4-11.4c13.7-9.6,31.1-6,37.8,4.7c5,8,4.1,19.5-2.4,26.8c-8.4,9.3-24.8,10.2-36.3,0"/>
</g>
<g id="letter-o">
<path class="letter" d="M1.7,23c-9.6,0-16.8-10.3-16.6-19.7c0.2-9.5,8-19.2,17.3-18.9C12.2-15.2,19.2-4,18.2,5.7
C17.4,13.9,10.7,23,1.7,23z"/>
</g>
<g id="letter-c">
<path class="letter" d="M18.2-13.6c0,0-27.3-21.4-32.5,17.1s32.5,17.7,32.5,17.7"/>
</g>
<g id="letter-a">
<path class="letter" d="M-12.2,27.5L0.5-20.1l18.6,43.8"/>
<path class="letter" d="M-15.6,12.4l34.7-8.7"/>
</g>
<g id="letter-b">
<path class="letter" d="M-4.5-16.5l-2.6,44.6"/>
<path class="letter" d="M-20.1-9.6c0,0,26.9-19.5,34.7-6.9C20.9-6.4,0,1.7,0,1.7s23.7-6.5,23.5,11.7c-0.3,25.7-39.4,6.8-39.4,6.8"/>
</g>
</defs>
<g id="stage"></g>
</svg>
</div>
<div id="toggle"><a href="#">Loading</a></div>
var SVG = Snap('#svg');
var container = $('.container');
var toggle = $('#toggle a');
var stage = Snap('#stage');
var colors = ['#f80c12', '#ff9933', '#d0c310', '#69d025', '#12bdb9', '#4444dd', '#442299'];
var sizes = {};
sizes.container = {width: 0, height: 0};
var availableLetters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
var letters = {};
var sentence = [];
var autoTypeSpeed = 250;
var blinkLine;
var blinkLineHeight = 70;
var multiColors = true;
toggle.bind('click', function()
{
multiColors = !multiColors;
updateColorsToggle();
})
function updateColorsToggle()
{
toggle.html('Multi-color is ' + (multiColors ? 'ON' : 'OFF'));
}
updateColorsToggle();
for(var i = 0; i < availableLetters.length; i++)
{
var letter = availableLetters[i];
letters[letter] = Snap('#letter-' + letter);
}
blinkLine = SVG.path('M0,0 0,' + blinkLineHeight)
.attr({
stroke: 'white',
'stroke-width': 3,
fill: 'none',
class: 'blinky-mcblinkface',
'stroke-linecap': "round"
})
//console.log(letters)
$(window).resize(onResize);
onResize();
// newLetter(letters.a);
// newLetter(letters.b);
// newLetter(letters.c);
var welcomeMessage = 'hello';
setTimeout(addWelcomeLetter, autoTypeSpeed)
function addWelcomeLetter()
{
if(welcomeMessage.length > 0)
{
onInput(welcomeMessage.substring(0, 1));
welcomeMessage = welcomeMessage.substring(1);
setTimeout(addWelcomeLetter, autoTypeSpeed)
}
else
{
setupInput();
}
}
function setupInput()
{
$(window).keydown(function(e)
{
e.preventDefault();
//console.log(e.key.toLowerCase())
onInput(e.key.toLowerCase());
})
$(window).focus();
}
function onInput(input)
{
if(letters[input])
{
sentence.push(newLetter(letters[input].clone()));
positionSentence();
}
else if(input == ' ')
{
sentence.push(null);
positionSentence();
}
else if(input == 'backspace')
{
removeLast();
}
}
function positionSentence()
{
var letterWidth = 40;
var letterGap = 10;
var leftLimit = (sizes.container.width / 100) * 80;
var widthLimit = (sizes.container.width / 100) * 60;
var canFitOnScreen = Math.floor(sizes.container.width / (letterWidth + letterGap));
if(canFitOnScreen < sentence.length)
{
var removed = sentence.splice(0, sentence.length - canFitOnScreen);
for(var i = 0; i < removed.length; i++)
{
if(removed[i]) removed[i].remove();
}
}
var sentenceWidth = ((letterWidth + letterGap) * sentence.length) - letterGap;
if(sentenceWidth > widthLimit)
{
var startLeft = leftLimit - sentenceWidth;
}
else
{
startLeft = (sizes.container.width - sentenceWidth) / 2;
}
var startTop = (sizes.container.height - (letterWidth/2)) / 2;
for(var i = 0; i < sentence.length; i++)
{
if(sentence[i]) TweenMax.to(sentence[i].node, 0.3, {transformOrign:'50% 50%', x: startLeft + ((letterGap + letterWidth) * i), y: startTop});
}
if(blinkLine) TweenMax.to(blinkLine.node, 0.3, {x: startLeft + sentenceWidth, y: (sizes.container.height - (blinkLineHeight)) / 2 - 5});
}
function onResize()
{
sizes.container.width = container.width();
sizes.container.height = container.height();
SVG.attr({
width: sizes.container.width,
height: sizes.container.height
})
}
function newLetter(letter)
{
var letterHolder = stage.group();
var paths = letter.selectAll('path');
var parts = [];
// the colorfull bits
for(var i = 0; i < paths.length; i++)
{
var path = paths[i];
parts = parts.concat(breakUp(path));
}
// full bits on top
for(var i = 0; i < paths.length; i++)
{
var path = paths[i];
parts = parts.concat(path);
}
// add all the parts together
var toAnimate = [];
for(var i = 0; i < parts.length; i++)
{
if(typeof parts[i] == 'string')
{
var newPath = letterHolder.path(parts[i])
newPath.attr({
class: 'letter',
'stroke-linecap': "round"
})
toAnimate.push(newPath);
newPath.node.style.fill = 'none';
newPath.node.style.opacity = 0.7;
newPath.node.style.strokeWidth = 10;// + (Math.random() * 10);
newPath.node.style.stroke = multiColors ? colors[Math.floor(Math.random()*colors.length)] : '#111';
}
else
{
parts[i].attr({
'stroke-linecap': "round"
})
parts[i].dontMove = true;
parts[i].node.style.strokeWidth = 5;
toAnimate.push(parts[i]);
letterHolder.append(parts[i]);
}
}
//
for(var i = 0; i < toAnimate.length; i++)
{
var path = toAnimate[i];
var length = path.getTotalLength();
var startOffset = Math.random() > 0.5 ? -length : length;
var strokeWidth = path.node.style.strokeWidth;
path.node.style.strokeDasharray = length + ' ' + length;
var xyOffset = 200;
if(path.dontMove)
{
TweenMax.fromTo(path.node, 1, {strokeWidth: 0, strokeDashoffset: startOffset}, {strokeWidth: strokeWidth, strokeDashoffset: 0, delay: 0.8, ease: Power4.easeInOut})
}
else
{
TweenMax.fromTo(
path.node,
1,
{
strokeWidth: 1,
x: '+' + ((Math.random() * xyOffset) - (xyOffset / 2)),
y: '+' + ((Math.random() * xyOffset )- (xyOffset / 2)),
scale: 1,
strokeDashoffset: startOffset
},
{
bezier: [
{
x: (Math.random() * xyOffset) - (xyOffset / 2),
y: (Math.random() * xyOffset) - (xyOffset / 2)
},
{
x: (Math.random() * xyOffset) - (xyOffset / 2),
y: (Math.random() * xyOffset) - (xyOffset / 2)
},
{
x: 0,
y: 0
}
],
strokeWidth: strokeWidth,
rotation: 0,
scale: 1,
//x: 0,
//y: 0,
strokeDashoffset: 0,
ease: Power3.easeOut,
delay: (i/ 100) + Math.random() /2
}
)
}
}
TweenMax.set(letterHolder.node, {transformOrign:'50% 50%'})
return letterHolder;
}
function breakUp(path)
{
var lengthChunks = 3;
var length = path.getTotalLength();
var count = length / lengthChunks;
var toReturn = [];
// get points along the path
var points = [];
for(var i = 0; i < count; i++)
{
var p = path.getPointAtLength( lengthChunks * i );
points.push([p.x, p.y]);
}
// group points into chunks
var chunks = [[]];
while(points.length)
{
if(chunks[0].length > 2 && points.length > 2 && Math.random() < 0.2)
{
chunks.unshift([chunks[0][chunks[0].length - 1]]);
}
chunks[0].push(points.shift());
}
// make path strings from chunks
for(var i = 0; i < chunks.length; i++)
{
var pathString = 'M'
for(var j = 0; j < chunks[i].length; j++)
{
pathString += chunks[i][j].join(',') + ' ';
}
toReturn.push(pathString);
}
return toReturn;
}
function removeLast()
{
if(sentence.length > 0)
{
var removed = sentence.splice(sentence.length - 1, 1);
for(var i = 0; i < removed.length; i++)
{
if(removed[i]) explodeLetter(removed[i])
}
positionSentence();
}
}
function explodeLetter(letter)
{
var bits = letter.selectAll('path').items;
for(var i = 0; i < bits.length; i++)
{
var func = i == 0 ? removeLetter : null;
var funcParams = i == 0 ? [bits[i]] : [];
TweenMax.to(bits[i].node, 0.4, {
x: Math.random() * (sizes.container.width / 2),
y: Math.random() * (sizes.container.height / 2),
scale: 0,
rotation: Math.random() * 360
});
}
}
function removeLetter(letter)
{
if(letter) letter.remove();
}
<script src="//codepen.io/steveg3003/pen/zBVakw"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.19.0/TweenMax.min.js"></script>
@import 'https://fonts.googleapis.com/css?family=Catamaran';
html, body
{
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: 'Catamaran', sans-serif;
}
body {
background: #eeeeff;
}
.container
{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
svg
{
z-index: 10;
.letter
{
fill: none;
stroke: #fff;
stroke-width: 5;
stroke-miterlimit: 10;
}
}
.blinky-mcblinkface
{
animation: 1s blink step-end infinite;
}
@keyframes "blink" {
from, to {
opacity: 0;
}
50% {
opacity: 1;
}
}
#toggle
{
position: fixed;
top: 10px;
left: 10px;
z-index: 100;
a
{
color: white;
text-decoration: none;
&:hover
{
color: #eee;
}
}
}
<link href="http://codepen.io/peet86/pen/rLXdWz" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment