├── .gitattributes ├── .gitignore ├── README.md ├── demos ├── 1 │ ├── demo1.html │ ├── demo1.js │ ├── maze.png │ ├── path.svg │ └── styles.css ├── 2 │ ├── demo2.html │ └── fire.png └── 3 │ ├── demo3.html │ ├── demo3.js │ ├── maze.png │ ├── path.svg │ └── styles.css ├── index.html └── pathAnimator.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Path Animator 2 | ============= 3 | Moves a DOM element along an SVG path (or do whatever along a path...) 4 | 5 | # [DEMO PAGE](http://yaireo.github.io/pathAnimator/) 6 | 7 | ## Basic use example: 8 | ```javascript 9 | 10 | var path = "M150 0 L75 200 L225 200 Z"; // an SVG path 11 | pathAnimator, 12 | startFromPercent = 10, // start from 10% of the path 13 | stopAtPercent = 70; // stop at 70% of the path (which will then call the onDone function callback) 14 | 15 | 16 | // initiate a new pathAnimator object 17 | pathAnimator = new PathAnimator( path, { 18 | duration : 4, // seconds that will take going through the whole path 19 | step : step, 20 | easing : function(t){ return t*(2-t) }, 21 | onDone : finish(this) 22 | }); 23 | 24 | pathAnimator.start( startFromPercent, stopAtPercent ); 25 | 26 | function step( point, angle ){ 27 | // do something every "frame" with: point.x, point.y & angle 28 | } 29 | 30 | function finish(){ 31 | // do something when animation is done 32 | } 33 | ``` 34 | 35 | ## Settings 36 | 37 | Name | Type | Default | Info 38 | ------------------- | ---------- | ----------- | -------------------------------------------------------------------------- 39 | duration | Number | undefined | (in seconds) the duration of going through the path 40 | step | Function | undefined | a callback function which is called on every frame 41 | onDone | Function | undefined | (optional) a callback function which will be called at the end 42 | reverse | Boolean | false | go back or forward along the path 43 | startPercent | Number | 0 | Where to start on the path, between 0% to 100% 44 | easing | Function | 1000/60 | (optional) mathematical function for easing 45 | fps | Number | undefined | (optional) frames per second 46 | 47 | -------------------------------------------------------------------------------- /demos/1/demo1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PathAnimator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | By – Yair Even Or 17 | 18 |
19 | 20 | 21 | 22 |

23 | 24 |
25 | 26 | 30 | 34 | 45 |
46 | 47 | Time (s) 48 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /demos/1/demo1.js: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------- 2 | Page Configuration 3 | -----------------------------------------------------------*/ 4 | (function(){ 5 | var path = "M6.426,79.957c0,11.458,1.996,19,14.175,19s14.513,3.233,14.513,13.992 c0,6.656-0.397,14.008-13.284,14.008c-9.987,0-15.716,7.74-15.716,13.369c0,21.011,0,61.556,0,82.832 c0,5.765,4.383,13.8,15.54,13.8c8.249,0,18.337,0,26.498,0c7.93,0,14.962,6.735,14.962,13.752c0,21.643,0,63.35,0,84.411 c0,6.236,6.625,12.837,13.29,12.837c26.493,0,85.584,0,111.78,0c8.115,0,12.93-6.952,12.93-13.364c0-21.212,0-62.342,0-83.445 c0-8.199,3.521-13.191,13.918-13.191c10.265,0,14.082,6.601,14.082,13.103c0,26.36,0,85.56,0,112.314 c0,5.3-3.583,13.583-12.941,13.583c-21.146,0-62.868,0-84.055,0c-5.23,0-13.004,4.118-13.004,12.652 c0,8.372,3.064,16.348,13.18,16.348c8.367,0,19.188,0,27.726,0c8.527,0,14.095,4.659,14.095,11.292 c0,7.924,5.049,13.708,14.07,13.708c31.402,0,106.853,0,137.575,0c9.209,0,13.355,8.605,13.355,13.932c0,26.855,0,85.454,0,111.787 c0,7.003,5.738,13.281,15.16,13.281c27.004,0,83.071,0,109.484,0c8.844,0,14.355,6.448,14.355,14.589 c0,10.933-5.415,16.411-13.775,16.411c-8.578,0-14.225,5.688-14.225,10.997c-1,21.253,16.501,34.67,39.834,32.67 s59.72-19.334,63.333-77.334s-38.419-58.724-2.876-143.362c35.543-84.637-31.851-132.554-23.66-194.708 c9.375-71.142,49.203-73.929,66.536-149.263c0-54.333-59.469-55.664-95.734-48.665s-48.265-18.999-91.599-20s-30,31-99.667,31 s-44.05-26.665-103.191-31c-59.142-4.335-38.976,40.167-121.642,41.832c-23.568,0-54.487-17.882-63.333-10.333 C12.211,47.827,7.582,70.632,6.53,77.041C6.376,77.978,6.426,79.895,6.426,79.957z", 6 | firstWalkerObj = $('.maze > .walker')[0], 7 | walkers = []; 8 | 9 | // handles whatever moves along the path 10 | function AnimateWalker(walker){ 11 | this.pathAnimator = new PathAnimator( path, { 12 | duration : 30, 13 | step : this.step.bind(this), 14 | reverse : false, 15 | onDone : this.finish.bind(this) 16 | }); 17 | 18 | this.walker = walker; 19 | this.color = 'deeppink'; // visually separate different walkers easily 20 | } 21 | 22 | AnimateWalker.prototype = { 23 | start : function(){ 24 | //this.walker.style.cssText = ""; 25 | this.startOffset = (this.reverse || this.speed < 0) ? 100 : 0; // if in reversed mode, then animation should start from the end, I.E 100% 26 | this.pathAnimator.start(); 27 | }, 28 | 29 | // Execute every "frame" 30 | step : function(point, angle){ 31 | this.walker.style.cssText = "top:" + point.y + "px;" + 32 | "left:" + point.x + "px;" + 33 | "transform:rotate(" + angle + "deg);" + 34 | "color:" + this.color; 35 | }, 36 | 37 | // Restart animation once it was finished 38 | finish : function(){ 39 | this.start(); 40 | }, 41 | 42 | // Resume animation from the last completed percentage (also updates the animation with new settings' values) 43 | resume : function( settings ){ 44 | settings = settings || {}; 45 | $.extend(this.pathAnimator.settings, settings); 46 | 47 | this.pathAnimator.start( this.pathAnimator.percent ); 48 | } 49 | } 50 | 51 | function generateWalker(walkerObj){ 52 | var newAnimatedWalker = new AnimateWalker( walkerObj ); 53 | walkers.push(newAnimatedWalker); 54 | return newAnimatedWalker; 55 | } 56 | 57 | // start "animating" the first Walker on the page 58 | generateWalker(firstWalkerObj).start(); 59 | // bind the first Controller to the first Walker 60 | var firstController = $('menu > div:first'); 61 | resetController( firstController ); 62 | firstController.data( 'walker', walkers[0] ); 63 | 64 | /*----------------------------------------------------------- 65 | User Controls 66 | ------------------------------------------------------------*/ 67 | $('#showPath').on('change', togglePath); 68 | $('#addWalker').on('click', addWalker); 69 | $('menu') 70 | .on('click', '.delete', removeInstance) 71 | .on('change', '.stopPlay input', stopPlay) 72 | .on('change', '.reverse input', switchDirection) 73 | .on('change', '.speed', changeSpeed) 74 | .on('change', 'select', changeEasing); 75 | 76 | $('.speed').trigger('change'); 77 | 78 | // show / hide the path of the animated object 79 | function togglePath(){ 80 | $('#svgPath').toggleClass('show'); 81 | } 82 | 83 | // add a new instance Walker and his controller box 84 | function addWalker(){ 85 | var newWalker = firstWalkerObj.cloneNode(true), 86 | controllerTemplate = $('menu > div:last'), 87 | controllerClone = controllerTemplate.clone(), 88 | newAnimatedWalker = generateWalker(newWalker), 89 | color = '#'+(Math.random()*0xFFFFFF<<0).toString(16); 90 | 91 | resetController( controllerClone ); 92 | controllerTemplate.after( controllerClone.css('borderColor', color) ); 93 | 94 | $(firstWalkerObj).after(newWalker); 95 | 96 | controllerClone.data('walker', newAnimatedWalker); // keep track which controller controls which walker 97 | newAnimatedWalker.color = color; 98 | newAnimatedWalker.start(); 99 | } 100 | // reset the controller box for new "walker" instances 101 | function resetController(obj){ 102 | var speed = 30; 103 | obj.find('.speed').val(speed).next().text(speed + 's'); 104 | obj.find(':checkbox').removeAttr('checked'); 105 | } 106 | 107 | // pause or place the animated object along the path 108 | function stopPlay(){ 109 | var thisAnimatedWalker = $(this.parentNode.parentNode).data('walker'); 110 | 111 | thisAnimatedWalker.pathAnimator.running ? thisAnimatedWalker.pathAnimator.stop() : thisAnimatedWalker.resume.apply(thisAnimatedWalker); 112 | } 113 | 114 | // switch direction of the animated object 115 | function switchDirection(){ 116 | var thisAnimatedWalker = $(this.parentNode.parentNode).data('walker'), 117 | settings = { 118 | reverse : !thisAnimatedWalker.pathAnimator.settings.reverse 119 | }; 120 | 121 | if( thisAnimatedWalker.pathAnimator.running ) 122 | thisAnimatedWalker.resume.call(thisAnimatedWalker, settings); 123 | } 124 | 125 | function changeSpeed(){ 126 | var thisAnimatedWalker = $(this.parentNode).data('walker'); 127 | this.nextElementSibling.innerHTML = this.value + 's'; 128 | 129 | thisAnimatedWalker.resume.call(thisAnimatedWalker, { duration:this.value }); 130 | } 131 | 132 | function removeInstance(){ 133 | var parent = $(this.parentNode), 134 | thisAnimatedWalker = parent.data('walker'); 135 | 136 | // make sure at least one Walker stays 137 | if( walkers.length > 1 ){ 138 | parent.remove(); 139 | thisAnimatedWalker.pathAnimator.stop(); 140 | $(thisAnimatedWalker.walker).remove(); 141 | walkers.splice(walkers.indexOf(thisAnimatedWalker), 1); 142 | } 143 | } 144 | 145 | function changeEasing(){ 146 | var thisAnimatedWalker = $(this.parentNode).data('walker'), 147 | formula = this.value; 148 | 149 | if( this.value ){ 150 | thisAnimatedWalker.resume.call(thisAnimatedWalker, { 151 | easing : function(t){ return eval(formula) } 152 | }); 153 | } 154 | } 155 | 156 | // reset checkboxes 157 | $(':checkbox').removeAttr('checked'); 158 | $('select').prop('selectedIndex', 0); 159 | })(); -------------------------------------------------------------------------------- /demos/1/maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yairEO/pathAnimator/84fb6ddc58d72aecccd6854d4595a0472bcbb78d/demos/1/maze.png -------------------------------------------------------------------------------- /demos/1/path.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demos/1/styles.css: -------------------------------------------------------------------------------- 1 | /* YUI 3.8.1 (build 5795) Copyright 2013 Yahoo! Inc. */ 2 | html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}#yui3-css-stamp.cssreset{display:none} 3 | .cf:before, .cf:after{content:""; display:table;} .cf:after {clear:both;} .cf{zoom:1;} 4 | 5 | html{ height:100%; } 6 | 7 | body{ color:#666; font:12px/1.5 arial; text-align:center; min-height:100%; } 8 | 9 | label{ cursor:pointer; } 10 | input[type=checkbox]{ margin:-1px 5px 0 0; vertical-align:text-top; } 11 | 12 | menu{ position:absolute; top:45px; left:0; padding:10px; margin:0; text-align:left; } 13 | .walkerController{ background:#EEE; padding:6px; margin:8px 0; width:300px; border-left:8px solid deeppink; } 14 | .walkerController input[type=checkbox]{ display:none; }{ } 15 | menu button{ cursor:pointer; margin:0 0 5px 0; font-size:inherit; } 16 | 17 | .walkerController label > span{ background:#E1E1E1; border:1px solid #AAA; padding:1px 6px; border-radius:2px; } 18 | .walkerController label > span:hover{ background:#EEE; } 19 | .walkerController input[type=range]{ width:230px; } 20 | .walkerController input[type=range] + span{ float:right; line-height:2; } 21 | .stopPlay span::after{ content:'Playing'; } 22 | .walkerController .stopPlay input:checked + span::after{ content:'Stopped'; color:red; } 23 | 24 | .reverse span::after{ content:'Going Forward'; } 25 | .walkerController .reverse input:checked + span::after{ content:'Going Reverse'; } 26 | 27 | menu > div:first-of-type .delete{ display:none; } 28 | menu select option:first-child{ border-bottom:1px solid #CCC; font-weight:bold; } 29 | menu .delete{ background:#FFF; border:1px solid #CCC; border-radius:30px; color:#666; float:right; font-size:20px; height:20px; line-height:8px; padding:0 0 1px; width:20px; } 30 | menu .delete:hover{ background:#666; color:#fff; border-color:transparent; } 31 | .walkerController input{ padding:4px; margin-right:6px; width:70px; vertical-align:middle; } 32 | menu #addWalker{ padding:5px 20px; } 33 | 34 | header{ background:#333; width:100%; height:40px; line-height:36px; position:fixed; top:0; left:0; font-size:14px; } 35 | a.git, a.by{ position:absolute; top:2px; right:5px; opacity:0.3; -webkit-transition:0.3s cubic-bezier(0.055, 0.6, 0.2, 1); transition:0.3s cubic-bezier(0.055, 0.6, 0.2, 1); } 36 | a.git{ -webkit-transform:scale(0.6); transform:scale(0.6); -webkit-transform-origin:100% 0 0; transform-origin:100% 0 0; } 37 | a.git:hover, a.by:hover{ opacity:1; } 38 | a.git img{ } 39 | 40 | a.by{ text-transform:capitalize; right:auto; left:5px; font-weight:bold; color:#FFF; text-decoration:none; font-family:'Fjalla One', sans-serif; -webkit-transform-origin:0%; transform-origin:0%; } 41 | .social{ position:absolute; top:-15px; left:5px; -webkit-filter:blur(20px); opacity:0; -webkit-transition:1s ease-out; transition:.6s ease-out; } 42 | .social.show{ top:5px; opacity:1; -webkit-filter:none; } 43 | .fbLike{ height:21px; width:100px; border:none; } 44 | .twitter-share-button{ } 45 | .social > *{ opacity:.5; -webkit-transition:.2s ease-out; transition:.2s ease-out; } 46 | .social > *:hover{ opacity:1; } 47 | 48 | /* graphics */ 49 | .maze{ display:inline-block; vertical-align:middle; height:537px; width:536px; margin:100px auto; background:url('maze.png') 0 0 no-repeat; position:relative; } 50 | /* Walker */ 51 | .walker{ position:absolute; z-index:1; font-size:25px; color:deeppink; height:25px; width:25px; text-align:center; line-height:25px; margin:-93px 0 0 28px; text-shadow:-6px 0px 2px rgba(0,0,0,0.3), 7px 0; } 52 | 53 | #svgPath{ opacity:0; margin:-80px 0 0 39px; -webkit-transition:0.2s; transition:0.2s; } 54 | #svgPath.show{ opacity:1; } -------------------------------------------------------------------------------- /demos/2/demo2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PathAnimator - demo 2 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /demos/2/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yairEO/pathAnimator/84fb6ddc58d72aecccd6854d4595a0472bcbb78d/demos/2/fire.png -------------------------------------------------------------------------------- /demos/3/demo3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PathAnimator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | By – Yair Even Or 16 | 17 |
18 | 19 | 20 |
21 | The walker will stop at some specific points, which are specified in percentage, and will continue after N seconds. 22 |

23 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demos/3/demo3.js: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------- 2 | Page Configuration 3 | -----------------------------------------------------------*/ 4 | (function(){ 5 | var path = "M6.426,79.957c0,11.458,1.996,19,14.175,19s14.513,3.233,14.513,13.992 c0,6.656-0.397,14.008-13.284,14.008c-9.987,0-15.716,7.74-15.716,13.369c0,21.011,0,61.556,0,82.832 c0,5.765,4.383,13.8,15.54,13.8c8.249,0,18.337,0,26.498,0c7.93,0,14.962,6.735,14.962,13.752c0,21.643,0,63.35,0,84.411 c0,6.236,6.625,12.837,13.29,12.837c26.493,0,85.584,0,111.78,0c8.115,0,12.93-6.952,12.93-13.364c0-21.212,0-62.342,0-83.445 c0-8.199,3.521-13.191,13.918-13.191c10.265,0,14.082,6.601,14.082,13.103c0,26.36,0,85.56,0,112.314 c0,5.3-3.583,13.583-12.941,13.583c-21.146,0-62.868,0-84.055,0c-5.23,0-13.004,4.118-13.004,12.652 c0,8.372,3.064,16.348,13.18,16.348c8.367,0,19.188,0,27.726,0c8.527,0,14.095,4.659,14.095,11.292 c0,7.924,5.049,13.708,14.07,13.708c31.402,0,106.853,0,137.575,0c9.209,0,13.355,8.605,13.355,13.932c0,26.855,0,85.454,0,111.787 c0,7.003,5.738,13.281,15.16,13.281c27.004,0,83.071,0,109.484,0c8.844,0,14.355,6.448,14.355,14.589 c0,10.933-5.415,16.411-13.775,16.411c-8.578,0-14.225,5.688-14.225,10.997c-1,21.253,16.501,34.67,39.834,32.67 s59.72-19.334,63.333-77.334s-38.419-58.724-2.876-143.362c35.543-84.637-31.851-132.554-23.66-194.708 c9.375-71.142,49.203-73.929,66.536-149.263c0-54.333-59.469-55.664-95.734-48.665s-48.265-18.999-91.599-20s-30,31-99.667,31 s-44.05-26.665-103.191-31c-59.142-4.335-38.976,40.167-121.642,41.832c-23.568,0-54.487-17.882-63.333-10.333 C12.211,47.827,7.582,70.632,6.53,77.041C6.376,77.978,6.426,79.895,6.426,79.957z", 6 | firstWalkerObj = $('.maze > .walker')[0], 7 | walkers = [], 8 | totalPercentageElm = document.querySelector('.totalPercentage'), 9 | stopPointElm = document.querySelector('.stopPoint'); 10 | 11 | // handles whatever moves along the path 12 | function AnimateWalker(walker){ 13 | this.pathAnimator = new PathAnimator( path, { 14 | duration : 4, 15 | step : this.step.bind(this), 16 | reverse : false, 17 | easing : function(t){ return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t }, 18 | onDone : this.onDone.bind(this) 19 | }); 20 | 21 | this.walker = walker; 22 | this.color = 'deeppink'; // visually separate different walkers easily 23 | } 24 | 25 | AnimateWalker.prototype = { 26 | init : function(){ 27 | this.stops = [20, 40, 80, 100]; 28 | this.stopIdx = 0; 29 | this.goToStop( 0, this.stops[0] ); 30 | }, 31 | 32 | /** 33 | * the percentage value of the overall path where the stop should occur 34 | * @param {Number} startFromPercent 35 | * @param {Number} stopAtPercent 36 | */ 37 | goToStop( startFromPercent, stopAtPercent ){ 38 | this.pathAnimator.start( startFromPercent, stopAtPercent ); 39 | }, 40 | 41 | // Execute every "frame" 42 | step : function(point, angle){ 43 | totalPercentageElm.innerHTML = this.pathAnimator.percent.toFixed(0); 44 | 45 | this.walker.style.cssText = "top:" + point.y + "px;" + 46 | "left:" + point.x + "px;" + 47 | "transform:rotate(" + angle + "deg);" + 48 | "color:" + this.color; 49 | }, 50 | 51 | // Restart animation once it was finished 52 | onDone : function(){ 53 | var that = this; 54 | stopPointElm.innerHTML = this.pathAnimator.percent == 100 ? 0 : this.stopIdx + 1; 55 | 56 | if( that.stopIdx < that.stops.length - 1 ){ 57 | setTimeout(function(){ 58 | that.goToStop( that.stops[that.stopIdx], that.stops[++that.stopIdx] ); 59 | }, 500) 60 | } 61 | else 62 | this.init(); 63 | }, 64 | 65 | // Resume animation from the last completed percentage (also updates the animation with new settings' values) 66 | resume : function( settings ){ 67 | settings = settings || {}; 68 | $.extend(this.pathAnimator.settings, settings); 69 | 70 | this.pathAnimator.start( this.pathAnimator.percent ); 71 | } 72 | } 73 | 74 | 75 | 76 | function generateWalker(walkerObj){ 77 | var newAnimatedWalker = new AnimateWalker( walkerObj ); 78 | walkers.push(newAnimatedWalker); 79 | return newAnimatedWalker; 80 | } 81 | 82 | // start "animating" the first Walker on the page 83 | generateWalker(firstWalkerObj).init(); 84 | 85 | // bind the first Controller to the first Walker 86 | var firstController = $('menu > div:first'); 87 | firstController.data( 'walker', walkers[0] ); 88 | })(); -------------------------------------------------------------------------------- /demos/3/maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yairEO/pathAnimator/84fb6ddc58d72aecccd6854d4595a0472bcbb78d/demos/3/maze.png -------------------------------------------------------------------------------- /demos/3/path.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /demos/3/styles.css: -------------------------------------------------------------------------------- 1 | /* YUI 3.8.1 (build 5795) Copyright 2013 Yahoo! Inc. */ 2 | html{color:#000;background:#FFF}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal}ol,ul{list-style:none}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}q:before,q:after{content:''}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea,select{*font-size:100%}legend{color:#000}#yui3-css-stamp.cssreset{display:none} 3 | .cf:before, .cf:after{content:""; display:table;} .cf:after {clear:both;} .cf{zoom:1;} 4 | 5 | html{ height:100%; } 6 | 7 | body{ color:#666; font:12px/1.5 arial; text-align:center; min-height:100%; } 8 | 9 | label{ cursor:pointer; } 10 | input[type=checkbox]{ margin:-1px 5px 0 0; vertical-align:text-top; } 11 | 12 | menu{ position:absolute; top:45px; left:0; padding:10px; margin:0; text-align:left; } 13 | .walkerController{ background:#EEE; padding:6px; margin:8px 0; width:282px; border-left:8px solid deeppink; } 14 | .walkerController input[type=checkbox]{ display:none; }{ } 15 | menu button{ cursor:pointer; margin:0 0 5px 0; font-size:inherit; } 16 | 17 | .walkerController label > span{ background:#E1E1E1; border:1px solid #AAA; padding:1px 6px; border-radius:2px; } 18 | .walkerController label > span:hover{ background:#EEE; } 19 | .walkerController input[type=range]{ width:230px; } 20 | .walkerController input[type=range] + span{ float:right; line-height:2; } 21 | .stopPlay span::after{ content:'Playing'; } 22 | .walkerController .stopPlay input:checked + span::after{ content:'Stopped'; color:red; } 23 | 24 | .reverse span::after{ content:'Going Forward'; } 25 | .walkerController .reverse input:checked + span::after{ content:'Going Reverse'; } 26 | 27 | menu > div:first-of-type .delete{ display:none; } 28 | menu select option:first-child{ border-bottom:1px solid #CCC; font-weight:bold; } 29 | menu .delete{ background:#FFF; border:1px solid #CCC; border-radius:30px; color:#666; float:right; font-size:20px; height:20px; line-height:8px; padding:0 0 1px; width:20px; } 30 | menu .delete:hover{ background:#666; color:#fff; border-color:transparent; } 31 | .walkerController input{ padding:4px; margin-right:6px; width:70px; vertical-align:middle; } 32 | menu #addWalker{ padding:5px 20px; } 33 | 34 | header{ background:#333; width:100%; height:40px; line-height:36px; position:fixed; top:0; left:0; font-size:14px; } 35 | a.git, a.by{ position:absolute; top:2px; right:5px; opacity:0.3; -webkit-transition:0.3s cubic-bezier(0.055, 0.6, 0.2, 1); transition:0.3s cubic-bezier(0.055, 0.6, 0.2, 1); } 36 | a.git{ -webkit-transform:scale(0.6); transform:scale(0.6); -webkit-transform-origin:100% 0 0; transform-origin:100% 0 0; } 37 | a.git:hover, a.by:hover{ opacity:1; } 38 | a.git img{ } 39 | 40 | a.by{ text-transform:capitalize; right:auto; left:5px; font-weight:bold; color:#FFF; text-decoration:none; font-family:'Fjalla One', sans-serif; -webkit-transform-origin:0%; transform-origin:0%; } 41 | .social{ position:absolute; top:-15px; left:5px; -webkit-filter:blur(20px); opacity:0; -webkit-transition:1s ease-out; transition:.6s ease-out; } 42 | .social.show{ top:5px; opacity:1; -webkit-filter:none; } 43 | .fbLike{ height:21px; width:100px; border:none; } 44 | .twitter-share-button{ } 45 | .social > *{ opacity:.5; -webkit-transition:.2s ease-out; transition:.2s ease-out; } 46 | .social > *:hover{ opacity:1; } 47 | 48 | /* graphics */ 49 | .maze{ display:inline-block; vertical-align:middle; height:537px; width:536px; margin:100px auto; background:url('maze.png') 0 0 no-repeat; position:relative; } 50 | /* Walker */ 51 | .walker{ position:absolute; z-index:1; font-size:25px; color:deeppink; height:25px; width:25px; text-align:center; line-height:25px; margin:-93px 0 0 28px; text-shadow:-6px 0px 2px rgba(0,0,0,0.3), 7px 0; } 52 | 53 | #svgPath{ opacity:0; margin:-80px 0 0 39px; -webkit-transition:0.2s; transition:0.2s; } 54 | #svgPath.show{ opacity:1; } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PathAnimator 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | By – Yair Even Or 17 | 18 |
19 | 20 | 21 | 22 |

23 | 24 |
25 | 26 | 30 | 34 | 45 |
46 | 47 | Time (s) 48 | 49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /pathAnimator.js: -------------------------------------------------------------------------------- 1 | // Path Animator v1.5.0 2 | // (c) 2013 Yair Even Or (https://github.com/yairEO/pathAnimator) 3 | // MIT-style license. 4 | 5 | function PathAnimator( path, settings ){ 6 | if( !path ) return; 7 | 8 | this.len = this.updatePath( path ); 9 | this.timer = null; 10 | 11 | this.settings = { 12 | duration : settings.duration, 13 | step : settings.step, 14 | reverse : !!settings.reverse, 15 | startPercent : settings.startPercent || 0, 16 | onDone : settings.onDone || function(){}, 17 | easing : settings.easing, 18 | fps : 1000/60, // frames-per-second 19 | } 20 | } 21 | 22 | PathAnimator.prototype = { 23 | start : function( startFromPercent, stopAtPercent ){ 24 | this.stop(); 25 | startFromPercent = startFromPercent || this.settings.startPercent || 0; 26 | this.percent = startFromPercent; 27 | if( this.settings.duration == 0 ) return false; 28 | 29 | var that = this, 30 | startTime = new Date(), 31 | stopAtPercent = stopAtPercent || 100; 32 | 33 | (function calc(){ 34 | var p = [], angle, 35 | now = new Date(), 36 | elapsed = (now-startTime)/1000, 37 | t = (elapsed/that.settings.duration), 38 | newPercent; 39 | 40 | // easing functions: https://gist.github.com/gre/1650294 41 | if( typeof that.settings.easing == 'function' ){ 42 | t = that.settings.easing(t); 43 | } 44 | 45 | newPercent = startFromPercent + t * (stopAtPercent - startFromPercent); 46 | 47 | if( that.settings.reverse ){ 48 | newPercent = startFromPercent - t * (stopAtPercent - startFromPercent) 49 | if( newPercent < 0 ) 50 | newPercent = stopAtPercent + newPercent; 51 | } 52 | 53 | that.running = true; 54 | that.percent = newPercent; 55 | 56 | // On animation end (from '0%' to '100%' or '100%' to '0%') 57 | if( t > 0.999 ){ 58 | that.stop(); 59 | that.percent = stopAtPercent; 60 | return that.settings.onDone(); 61 | } 62 | 63 | // angle calculations 64 | p[0] = that.pointAt( that.percent - 1 ); 65 | p[1] = that.pointAt( that.percent + 1 ); 66 | angle = Math.atan2(p[1].y-p[0].y,p[1].x-p[0].x)*180 / Math.PI; 67 | 68 | // advance to the next point on the path 69 | that.timer = setTimeout( calc, that.settings.fps ); 70 | // do one step ("frame") 71 | that.settings.step( that.pointAt(that.percent), angle ); 72 | })(); 73 | }, 74 | 75 | stop : function(){ 76 | clearTimeout( this.timer ); 77 | this.timer = null; 78 | this.running = false; 79 | }, 80 | 81 | stopAt : function(percent){ 82 | 83 | }, 84 | 85 | pointAt : function(percent){ 86 | return this.path.getPointAtLength( this.len * percent/100 ); 87 | }, 88 | 89 | updatePath : function( path ){ 90 | if( !this.path && path ){ 91 | this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 92 | this.path.setAttribute('d', path); 93 | } 94 | return this.path.getTotalLength(); 95 | } 96 | }; --------------------------------------------------------------------------------