235

I want to have a web page which has one centered word.

I want this word to be drawn with an animation, such that the page "writes" the word out the same way that we would, i.e. it starts at one point and draws lines and curves over time such that the end result is a glyph.

I do not care if this is done with <canvas> or the DOM, and I don't care whether it's done with JavaScript or CSS. The absence of jQuery would be nice, but not required.

How can I do this? I've searched exhaustively with no luck.

strugee
  • 2,752
  • 4
  • 19
  • 30
  • 2
    I put some thought into how to actually "handwrite" characters and posted my thoughts here: http://stackoverflow.com/questions/12700731/extract-path-from-text-html-canvas/29707525#29707525 – markE Apr 28 '15 at 15:27
  • There's something really similar in a [codrops article](http://tympanus.net/codrops/2013/12/30/svg-drawing-animation/) (with a [demo in tympanus](http://tympanus.net/Development/SVGDrawingAnimation/)) – Francisco Presencia May 04 '15 at 14:40
  • 1
    Back in the days, I was doing this animation in Flash using animated sprite masks. What you need is to animate a mask, which means having it progressively reveal text. The animation would be made of mask frames. – oxygen May 04 '15 at 17:49
  • Of course, you would have the benefit of beeing able of breaking text into curves. You would have to do this using before hand uising SVGs and some SVG editor (Illustrator, or whatever else can create a SVG of your text). I don't know if SVGs support masks, but if they do, this would become much easier to animate. – oxygen May 04 '15 at 17:54
  • Use SVG and manipulate the SVG code with JavaScript to make the animation. – The Demz May 04 '15 at 19:31
  • Easiest and most universal answer might be just embe an animated gif. Even if people turn off javascript or flash it would still work. – Jonathon May 04 '15 at 21:14
  • My plugin do this with options : http://codecanyon.net/item/responsive-svg-handwriting-font-animation-script/full_screen_preview/6719796 – user2267379 Feb 19 '16 at 05:52
  • Use typer.js, very useful – Chris Apr 08 '18 at 21:13
  • @ChristofferHjärtström that does not appear to answer the question at all. – strugee Apr 08 '18 at 22:23
  • Oh my word, didn't see the "animated" part, my bad! – Chris Apr 09 '18 at 12:52
  • Hey, I've edited and added a new realistic snippet, check it out. – Akshay Jan 16 '19 at 14:25

4 Answers4

275

I want this word to be drawn with an animation, such that the page "writes" the word out the same way that we would

Canvas version

This will draw single chars more like one would write by hand. It uses a long dash-pattern where the on/off order is swapped over time per char. It also has a speed parameter.

Snapshot
Example animation (see demo below)

To increase realism and the organic feel, I added random letter-spacing, an y delta offset, transparency, a very subtle rotation and finally using an already "handwritten" font. These can be wrapped up as dynamic parameters to provide a broad range of "writing styles".

For a even more realistic look the path data would be required which it isn't by default. But this is a short and efficient piece of code which approximates hand-written behavior, and easy to implement.

How it works

By defining a dash pattern we can create marching ants, dotted lines and so forth. Taking advantage of this by defining a very long dot for the "off" dot and gradually increase the "on" dot, it will give the illusion of drawing the line on when stroked while animating the dot length.

Since the off dot is so long the repeating pattern won't be visible (the length will vary with the size and characteristics of the typeface being used). The path of the letter will have a length so we need to make sure we are having each dot at least covering this length.

For letters that consists of more than one path (f.ex. O, R, P etc.) as one is for the outline, one is for the hollow part, the lines will appear to be drawn simultaneously. We can't do much about that with this technique as it would require access to each path segment to be stroked separately.

Compatibility

For browsers that don't support the canvas element an alternative way to show the text can be placed between the tags, for example a styled text:

<canvas ...>
    <div class="txtStyle">STROKE-ON CANVAS</div>
</canvas>

Demo

This produces the live animated stroke-on (no dependencies) -

var ctx = document.querySelector("canvas").getContext("2d"),
    dashLen = 220, dashOffset = dashLen, speed = 5,
    txt = "STROKE-ON CANVAS", x = 30, i = 0;

ctx.font = "50px Comic Sans MS, cursive, TSCu_Comic, sans-serif"; 
ctx.lineWidth = 5; ctx.lineJoin = "round"; ctx.globalAlpha = 2/3;
ctx.strokeStyle = ctx.fillStyle = "#1f2f90";

(function loop() {
  ctx.clearRect(x, 0, 60, 150);
  ctx.setLineDash([dashLen - dashOffset, dashOffset - speed]); // create a long dash mask
  dashOffset -= speed;                                         // reduce dash length
  ctx.strokeText(txt[i], x, 90);                               // stroke letter

  if (dashOffset > 0) requestAnimationFrame(loop);             // animate
  else {
    ctx.fillText(txt[i], x, 90);                               // fill final letter
    dashOffset = dashLen;                                      // prep next char
    x += ctx.measureText(txt[i++]).width + ctx.lineWidth * Math.random();
    ctx.setTransform(1, 0, 0, 1, 0, 3 * Math.random());        // random y-delta
    ctx.rotate(Math.random() * 0.005);                         // random rotation
    if (i < txt.length) requestAnimationFrame(loop);
  }
})();
canvas {background:url(http://i.imgur.com/5RIXWIE.png)}
<canvas width=630></canvas>
  • 21
    Am I the only one going crazy for this? Looks so real, at least, way better than the first answer, and closest to the questioner's question. – KhoPhi May 04 '15 at 21:01
  • 5
    I ended up using the other answer, because I needed it right away as a quick-n-dirty hack that I used the next day and then never touched again, but I'm accepting this one because it's way closer to what I was looking for. – strugee May 07 '15 at 04:27
  • 1
    How can we make it for multiple lines, and a long text block? – Saad Farooq May 13 '15 at 17:35
  • @keyser lol. well, that's what you get when "hand-drawing" text ... ;) –  May 20 '15 at 12:15
  • how to write Arabic clause (Right to left) like "مرحبا" – Ali Al-arnous Feb 12 '16 at 23:27
  • 1
    @AliAl-arnous you could set x to the opposite end, subtract char width instead of adding, transform with negated negative space and change clearRect to clear on the other side of the char. –  Apr 02 '16 at 01:41
  • How can I reduce the delay between each letter? – Arvigeus Apr 12 '18 at 09:55
  • @Arvigeus replace the last requestAnimationFrame with setTimeout and the delay you want. –  May 01 '18 at 21:38
  • @epistemex I don't want to increase the delay, I want to decrease it. For some reason it waits some time before painting the next char. – Arvigeus May 17 '18 at 11:14
  • Hey, how to make this thing print new lines?? – Musafiroon Sep 15 '22 at 12:33
  • saved me days of work and looks even better than being drawn animation. plus just few lines of code. thank you for answering. – Galzor Apr 24 '23 at 07:32
232

Edit 2019


I created a javascript library that can create realistic animations. It's easy to use and requires a special JSON file that acts as font.

var vara = new Vara("#container", "https://rawcdn.githack.com/akzhy/Vara/ed6ab92fdf196596266ae76867c415fa659eb348/fonts/Satisfy/SatisfySL.json", [{
  text: "Hello World!!",
  fontSize: 48,
  y:10
}, {
  text: "Realistic Animations",
  fontSize: 34,
  color:"#f44336"
}], {
  strokeWidth: 2,
  textAlign:"center"
});
#container {
  padding: 30px;
}
<script src="https://rawcdn.githack.com/akzhy/Vara/16e30acca2872212e28735cfdbaba696a355c780/src/vara.min.js"></script>
<div id="container"></div>

Checkout the Github page for documentation and examples. And Codepen


Previous Answer

The below example uses snap.js to dynamically create tspan elements and then animate each of their stroke-dashoffset.

var s = Snap('svg');
var text = 'Some Long Text'
var len = text.length;
var array = [];
for (var x = 0; x < len; x++) {
  var t = text[x]
  array.push(t);
}
var txt = s.text(50, 50, array)
$('tspan').css({
  'font-size': 50,
  fill: 'none',
  stroke: 'red',
  "stroke-width":2,
  'stroke-dasharray': 300,
  'stroke-dashoffset': 300
})

$('tspan').each(function(index) {
  $(this).stop(true, true).delay(300 * index).animate({
    'stroke-dashoffset': 0,
  }, 300, function() {
    $(this).css('fill', 'red')
  })
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="500" height="500">
</svg>

Previous Answer


You can do something like this using svg's stroke-dasharray

text {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  -webkit-animation: draw 8s forwards;
}
@-webkit-keyframes draw {
  100% {
    stroke-dashoffset: 0;
  }
}
text {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  -webkit-animation: draw 8s forwards;
  -moz-animation: draw 8s forwards;
  -o-animation: draw 8s forwards;
  -ms-animation: draw 8s forwards;
  animation: draw 8s forwards;
}
@-webkit-keyframes draw {
  100% {
    stroke-dashoffset: 0;
  }
}
@-moz-keyframes draw {
  100% {
    stroke-dashoffset: 0;
  }
}
@-o-keyframes draw {
  100% {
    stroke-dashoffset: 0;
  }
}
@-ms-keyframes draw {
  100% {
    stroke-dashoffset: 0;
  }
}
@keyframes draw {
  100% {
    stroke-dashoffset: 0;
  }
}
<svg width="500" height="500">
  <text x="100" y="80" fill="none" stroke="black" stroke-width="1" font-size="50">Some text</text>
</svg>

Without keyframes animation you can do something like this

<svg width="500" height="500">
  <text x="100" y="80" fill="none" stroke="black" stroke-width="5" font-size="50"  stroke-dasharray="1000"
  stroke-dashoffset="1000">Some text
  <animate attributeName="stroke-dashoffset"
    from="1000"
    to="0" 
    dur="8s"
      fill="freeze">
          
      </animate> </text>
</svg>

And for IE support you can use jquery/javascript

$('text').animate({
    'stroke-dashoffset':'0'
},8000)
text {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="500" height="500">
  <text x="100" y="80" fill="none" stroke="black" stroke-width="1" font-size="50" 
 >Some text
  </text>
</svg>
Community
  • 1
  • 1
Akshay
  • 14,138
  • 5
  • 46
  • 70
  • 4
    Wow, that's really interesting. I've taken your original code snippet and improved it somwhat by removing duplicate CSS properties, using percentage-based offsets and dasharray values, and changing the stroke width to make it more apparent how the code is working: http://jsfiddle.net/Ajedi32/gdc4azLn/1/ Feel free to edit any of those improvements into your answer if you want. – Ajedi32 Apr 28 '15 at 13:52
  • 2
    this is a one epic solution for this question, SVG is better than canvas (+1) as far that in case the browser doesn't support it it will show the text instead. – Jeffery ThaGintoki May 04 '15 at 14:50
  • 3
    @JefferyThaGintoki you can do this with canvas as well, just place the text between the canvas tags, if canvas isn't supported the text (or animated gif as in the other answer shows in the body text) will appear instead. If the browser does not support canvas it will probably not support svg either. – torox May 04 '15 at 22:58
  • 1
    I think the use of SVG text is great, however i'm wondering, is it possible to add the fill at some point? just the outline of the letters don't look nice in all projects, cheers! and of course +1 to this answer – Carlos Valencia Aug 30 '16 at 18:14
  • 1
    @randomguy04 Check the first snippet, i have edited it to add a fill effect. It is done by adding `$(this).css('fill', 'red')` as a callback to the animation – Akshay Aug 31 '16 at 03:17
  • does this is support in safari? – UserEsp Jul 23 '19 at 14:06
  • @UserEsp I haven't tested it on a safari. But it should work. – Akshay Jul 23 '19 at 14:20
  • Is there an easier way to create custom fonts? – SRR Sep 10 '19 at 15:44
  • @S.Ramjit Unfortunately no, you will have to manually draw those. – Akshay Sep 10 '19 at 16:05
  • Vara library is very useful in animating text characters. – Frederic Anand Sep 02 '20 at 07:15
1

Only CSS :

@keyframes fadein_left {
  from {
    left: 0;
  }
  to {
    left: 100%;
  }
}

#start:before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0%;
  opacity: 0.7;
  height: 25px;
  background: #fff;
  animation: fadein_left 3s;
}
<div id="start">
  some text some text some text some text some text
</div>
strugee
  • 2,752
  • 4
  • 19
  • 30
zloctb
  • 10,592
  • 8
  • 70
  • 89
0

Following many tests, here is some notes. The goal is to display fast text data in the least blocking way, on DOM heavy pages requiring users interactions.

There is of course many ways to achieve the same thing. On this example, the differences might not be obvious, it really apply to complex interfaces.

Slowest: innerHTML and inline styling. The DOM is recalculated at each iterations. The browser is working hard to keep the train. It will fails quickly, causing memory leaks and freezes:

setInterval(function(){
  out.innerHTML = `<span style="position:fixed;top:${~~(Math.random() * 220)}px">${Math.random() * 1000}<span>`
},1)
<h1 id="out"></h1>

Way better: Using textContent, requestAnimationFrame and the web animation api. This goes way smoother, it's obvious on DOM heavy pages. The user interactions won't blocks the repaints. Some repaints might be skipped, to keep the interface well responsive.

let job
const paint = () => {
  job = requestAnimationFrame(paint)
  out.textContent = Math.random() * 1000
  out.animate([{top: ~~(Math.random() * 220)+"px"},{top:0}],{duration: 1,iterations: 1})
}

/* Start looping -----------------------------------------*/
requestAnimationFrame(paint)
#out{
position: fixed}
<h1 id="out"></h1>

On the above example, the DOM is still being recalculated for the text overflow.. We can see the debugger blinking hard. This really matter on cascading elements! This can still slows down javascript and user scrollings.

enter image description here

Full power: It's possible to use css alone to refresh the data with the css content rule and css variables. The text won't then be selectable.

let job
const paint = () => {
  job = requestAnimationFrame(paint)
  out.setAttribute('data-before', Math.random() * 1000)
  out.animate([{top: ~~(Math.random() * 220)+"px"},{top:0}],{duration: 1,iterations: 1})
}

/* Start looping -----------------------------------------*/
requestAnimationFrame(paint)
#out{
  position: fixed
  
  }
#out:before {
   content: attr(data-before)
 }
<h1 id="out"></h1>

enter image description here

My tests showns great improvements, the javascript engine is skipping quickly on other tasks. Sometimes it can starts a bit slower than the above example. But beside that,this doesn't blocks users scrolls, and the debugger is also liking, no more jumpings.

NVRM
  • 11,480
  • 1
  • 88
  • 87