Skip to content

Instantly share code, notes, and snippets.

@abernier
Last active March 17, 2021 11:17
Show Gist options
  • Save abernier/e082a201b0865de1a41f to your computer and use it in GitHub Desktop.
Save abernier/e082a201b0865de1a41f to your computer and use it in GitHub Desktop.
Scribble.js

Trace SVG paths.

INSTALL

npm install https://gist.github.com/abernier/e082a201b0865de1a41f/archive/42f199f7137d4e5137b4e2f879f58db2c213d8c9.tar.gz

or

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
<script src="https://rawgit.com/tweenjs/tween.js/v16.3.4/src/Tween.js"></script>
<script src="https://rawgit.com/caolan/async/v1.5.2/dist/async.js"></script>
<script src="https://rawgit.com/abernier/e082a201b0865de1a41f/raw/69eeade18397b8ac525e5d022b4876d98d838ccb/index.js"></script>

USAGE

see: https://gist.github.com/abernier/e082a201b0865de1a41f#file-index-html-L31

<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,initial-scale=1,user-scalable=no">
<style>
html, body {height:100%;}
html {display:table; width:100%;}
body {display:table-cell; vertical-align:middle;}
svg {max-height:100vh; max-width:100vw;}
</style>
</head>
<body>
<svg viewBox="0 0 60.769 13.233" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M60.197,11.462c-0.42,0.636-0.96,1.271-2.447,1.271 c-1.332,0-2.471-1.08-2.471-2.507v-3.13c0-1.391,1.139-2.471,2.471-2.471c1.355,0,2.519,1.08,2.519,2.471v1.559h-4.978"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M51.187,12.613c-0.684,0-1.259-0.42-1.259-1.307V0.62"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M40.664,0.62v11.993V0.62z M40.675,10.263 c0,1.391,1.127,2.471,2.459,2.471c1.331,0,2.458-1.08,2.458-2.471V7.096c0-1.391-1.127-2.471-2.458-2.471 c-1.332,0-2.471,1.08-2.471,2.471L40.675,10.263z"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M31.171,0.62v11.993V0.62z M31.183,10.263 c0,1.391,1.127,2.471,2.459,2.471c1.331,0,2.458-1.08,2.458-2.471V7.096c0-1.391-1.127-2.471-2.458-2.471 c-1.332,0-2.471,1.08-2.471,2.471L31.183,10.263z"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M26.492,1.16V0.62V1.16z M26.492,12.613V4.686V12.613z"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M19.352,6.233c0.228-0.875,0.935-1.607,1.919-1.607 c0.551,0,0.899,0.324,1.127,0.792 M19.352,4.686v7.928V4.686z"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M15.605,10.263c0,1.391-1.127,2.471-2.458,2.471 c-1.332,0-2.471-1.08-2.471-2.471V7.096c0-1.391,1.139-2.471,2.471-2.471c1.331,0,2.458,1.08,2.458,2.471"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M6.449,3.366C6.232,1.399,5.129,0.5,3.57,0.5 c-1.571,0-2.962,1.211-2.962,2.818c0,1.248,0.54,1.655,1.547,2.255L4.854,7.18c1.163,0.696,1.763,1.235,1.763,2.687 c0,1.607-1.463,2.867-3.046,2.867c-1.751,0-3.07-1.259-3.07-2.927"/>
</svg>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
<script src="https://rawgit.com/tweenjs/tween.js/v16.3.4/src/Tween.js"></script>
<script src="https://rawgit.com/caolan/async/v1.5.2/dist/async.js"></script>
<script src="index.js"></script>
<script>
(function () {
var svg = document.querySelector('svg');
function onload() {
console.log('onload');
var paths = svg.querySelectorAll('path');
var scribble = new Scribble(paths, {duration: 3000});
scribble.erase();
scribble.draw(function () {
console.log('scribbled!'); // done
});
}
svg.addEventListener('load', onload, false);
function animate() {
TWEEN.update();
requestAnimationFrame(animate);
}
animate();
}).call(this);
</script>
</body>
</html>
(function () {
var _ = this._ || require('underscore');
var TWEEN = this.TWEEN || require('tween');
var async = this.async || require('async');
// see: http://jakearchibald.com/2013/animated-line-drawing-svg/
function Path(el, options) {
options || (options = {});
_.defaults(options, {
duration: 1000
});
this.options = options;
this.el = el;
var length = el.getTotalLength();
this.length = length;
// Set up the starting positions
el.style.strokeDasharray = length + ' ' + length;
el.style.strokeDashoffset = length;
this.update(1);
}
Path.prototype.update = function (val) {
this.val = val;
};
Path.prototype.paint = function () {
this.el.style.strokeDashoffset = this.length * (1 - this.val);
};
Path.prototype.start = function (cb) {
this.tween && this.tween.stop();
var o = {t: 0};
this.tween = new TWEEN.Tween(o).to({t: 1}, this.options.duration)
.onUpdate(function () {
this.update(o.t);
this.paint();
}.bind(this))
.onComplete(function () {
//console.log('complete');
cb(null);
})
.start()
;
};
Path.prototype.stop = function () {
this.tween && this.tween.stop();
};
Path.prototype.erase = function () {
this.stop();
this.tween = undefined;
this.update(0);
this.paint();
};
function Scribble(pathsEls, options) {
options || (options = {});
_.defaults(options, {
duration: 2000,
durationPerPath: false
});
this.options = options;
var paths = []; this.paths = paths;
var arr = []; this.arr = arr;
var i = pathsEls.length;
var totalLength = 0;
while(i--) {
var path = new Path(pathsEls[i], {duration: this.options.duration});
paths.push(path);
arr.push(path.start.bind(path));
totalLength += path.length;
}
// normalize duration
if (this.options.durationPerPath !== true) {
var totalDuration = this.options.duration;
var i = pathsEls.length;
while (i--) {
var path = paths[i];
var percent = paths[i].length / totalLength;
//if (percent > 2*1/pathsEls.length) continue;
paths[i].options.duration = percent * totalDuration;
}
}
var drawing = false;
}
Scribble.prototype.draw = function (cb) {
if (this.drawing) return;
this.drawing = true;
async.series(this.arr, cb); // https://github.com/caolan/async#seriestasks-callback
};
Scribble.prototype.erase = function () {
this.paths.forEach(function (path) {
path.erase();
});
this.drawing = false;
};
// Exports
this.Scribble = Scribble;
if (typeof module !== "undefined" && module !== null) {
module.exports = this.Scribble;
}
}).call(this);
{
"name": "scribble",
"version": "1.0.0",
"description": "scribble SVG paths",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gist.github.com/e082a201b0865de1a41f.git"
},
"author": "Antoine BERNIER",
"license": "ISC",
"dependencies": {
"async": "^1.4.0",
"tween": "^0.9.0",
"underscore": "^1.8.3"
}
}
@abernier
Copy link
Author

abernier commented Aug 3, 2015

@marijnhosten
Copy link

marijnhosten commented Apr 28, 2016

Would you happen to have the path data values for the each letter?

update: I found another library that tranforms text in svg paths, combined it with your animations for a nice result, thanks! :)

@zhongguogu
Copy link

@abernier how to reset the path after Animation. thank you very much! Waiting for your reply.

@timschoch
Copy link

Nice stuff @abernier! I've thought about using this on a slide deck for a presentation and on a landing page, but it doesn't work in the latest firefox. Any idea why?
Also, since this is a fairly old library, would you still recommend using it, or is there a newer approach to this?
Cheers, Tim

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment