anime.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. * Anime v1.0.0
  3. * http://anime-js.com
  4. * Javascript animation engine
  5. * Copyright (c) 2016 Julian Garnier
  6. * http://juliangarnier.com
  7. * Released under the MIT license
  8. */
  9. var anime = (function() {
  10. // Defaults
  11. var defaultSettings = {
  12. duration: 1000,
  13. delay: 0,
  14. loop: false,
  15. autoplay: true,
  16. direction: 'normal',
  17. easing: 'easeOutElastic',
  18. elasticity: 400,
  19. round: false,
  20. begin: undefined,
  21. update: undefined,
  22. complete: undefined
  23. }
  24. var validTransforms = ['translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', 'skewX', 'skewY'];
  25. // Utils
  26. var is = (function() {
  27. return {
  28. array: function(a) { return Array.isArray(a) },
  29. object: function(a) { return Object.prototype.toString.call(a).indexOf('Object') > -1 },
  30. html: function(a) { return (a instanceof NodeList || a instanceof HTMLCollection) },
  31. node: function(a) { return a.nodeType },
  32. svg: function(a) { return a instanceof SVGElement },
  33. number: function(a) { return !isNaN(parseInt(a)) },
  34. string: function(a) { return typeof a === 'string' },
  35. func: function(a) { return typeof a === 'function' },
  36. undef: function(a) { return typeof a === 'undefined' },
  37. null: function(a) { return typeof a === 'null' },
  38. hex: function(a) { return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a) },
  39. rgb: function(a) { return /^rgb/.test(a) },
  40. rgba: function(a) { return /^rgba/.test(a) },
  41. hsl: function(a) { return /^hsl/.test(a) },
  42. color: function(a) { return (is.hex(a) || is.rgb(a) || is.rgba(a) || is.hsl(a))}
  43. }
  44. })();
  45. // Easings functions adapted from http://jqueryui.com/
  46. var easings = (function() {
  47. var eases = {};
  48. var names = ['Quad', 'Cubic', 'Quart', 'Quint', 'Expo'];
  49. var functions = {
  50. Sine: function(t) { return 1 - Math.cos( t * Math.PI / 2 ); },
  51. Circ: function(t) { return 1 - Math.sqrt( 1 - t * t ); },
  52. Elastic: function(t, m) {
  53. if( t === 0 || t === 1 ) return t;
  54. var p = (1 - Math.min(m, 998) / 1000), st = t / 1, st1 = st - 1, s = p / ( 2 * Math.PI ) * Math.asin( 1 );
  55. return -( Math.pow( 2, 10 * st1 ) * Math.sin( ( st1 - s ) * ( 2 * Math.PI ) / p ) );
  56. },
  57. Back: function(t) { return t * t * ( 3 * t - 2 ); },
  58. Bounce: function(t) {
  59. var pow2, bounce = 4;
  60. while ( t < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
  61. return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - t, 2 );
  62. }
  63. }
  64. names.forEach(function(name, i) {
  65. functions[name] = function(t) {
  66. return Math.pow( t, i + 2 );
  67. }
  68. });
  69. Object.keys(functions).forEach(function(name) {
  70. var easeIn = functions[name];
  71. eases['easeIn' + name] = easeIn;
  72. eases['easeOut' + name] = function(t, m) { return 1 - easeIn(1 - t, m); };
  73. eases['easeInOut' + name] = function(t, m) { return t < 0.5 ? easeIn(t * 2, m) / 2 : 1 - easeIn(t * -2 + 2, m) / 2; };
  74. });
  75. eases.linear = function(t) { return t; };
  76. return eases;
  77. })();
  78. // Strings
  79. var numberToString = function(val) {
  80. return (is.string(val)) ? val : val + '';
  81. }
  82. var stringToHyphens = function(str) {
  83. return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  84. }
  85. var selectString = function(str) {
  86. if (is.color(str)) return false;
  87. try {
  88. var nodes = document.querySelectorAll(str);
  89. return nodes;
  90. } catch(e) {
  91. return false;
  92. }
  93. }
  94. // Numbers
  95. var random = function(min, max) {
  96. return Math.floor(Math.random() * (max - min + 1)) + min;
  97. }
  98. // Arrays
  99. var flattenArray = function(arr) {
  100. return arr.reduce(function(a, b) {
  101. return a.concat(is.array(b) ? flattenArray(b) : b);
  102. }, []);
  103. }
  104. var toArray = function(o) {
  105. if (is.array(o)) return o;
  106. if (is.string(o)) o = selectString(o) || o;
  107. if (is.html(o)) return [].slice.call(o);
  108. return [o];
  109. }
  110. var arrayContains = function(arr, val) {
  111. return arr.some(function(a) { return a === val; });
  112. }
  113. var groupArrayByProps = function(arr, propsArr) {
  114. var groups = {};
  115. arr.forEach(function(o) {
  116. var group = JSON.stringify(propsArr.map(function(p) { return o[p]; }));
  117. groups[group] = groups[group] || [];
  118. groups[group].push(o);
  119. });
  120. return Object.keys(groups).map(function(group) {
  121. return groups[group];
  122. });
  123. }
  124. var removeArrayDuplicates = function(arr) {
  125. return arr.filter(function(item, pos, self) {
  126. return self.indexOf(item) === pos;
  127. });
  128. }
  129. // Objects
  130. var cloneObject = function(o) {
  131. var newObject = {};
  132. for (var p in o) newObject[p] = o[p];
  133. return newObject;
  134. }
  135. var mergeObjects = function(o1, o2) {
  136. for (var p in o2) o1[p] = !is.undef(o1[p]) ? o1[p] : o2[p];
  137. return o1;
  138. }
  139. // Colors
  140. var hexToRgb = function(hex) {
  141. var rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  142. var hex = hex.replace(rgx, function(m, r, g, b) { return r + r + g + g + b + b; });
  143. var rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  144. var r = parseInt(rgb[1], 16);
  145. var g = parseInt(rgb[2], 16);
  146. var b = parseInt(rgb[3], 16);
  147. return 'rgb(' + r + ',' + g + ',' + b + ')';
  148. }
  149. var hslToRgb = function(hsl) {
  150. var hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hsl);
  151. var h = parseInt(hsl[1]) / 360;
  152. var s = parseInt(hsl[2]) / 100;
  153. var l = parseInt(hsl[3]) / 100;
  154. var hue2rgb = function(p, q, t) {
  155. if (t < 0) t += 1;
  156. if (t > 1) t -= 1;
  157. if (t < 1/6) return p + (q - p) * 6 * t;
  158. if (t < 1/2) return q;
  159. if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
  160. return p;
  161. }
  162. var r, g, b;
  163. if (s == 0) {
  164. r = g = b = l;
  165. } else {
  166. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  167. var p = 2 * l - q;
  168. r = hue2rgb(p, q, h + 1/3);
  169. g = hue2rgb(p, q, h);
  170. b = hue2rgb(p, q, h - 1/3);
  171. }
  172. return 'rgb(' + r * 255 + ',' + g * 255 + ',' + b * 255 + ')';
  173. }
  174. var colorToRgb = function(val) {
  175. if (is.rgb(val) || is.rgba(val)) return val;
  176. if (is.hex(val)) return hexToRgb(val);
  177. if (is.hsl(val)) return hslToRgb(val);
  178. }
  179. // Units
  180. var getUnit = function(val) {
  181. return /([\+\-]?[0-9|auto\.]+)(%|px|pt|em|rem|in|cm|mm|ex|pc|vw|vh|deg)?/.exec(val)[2];
  182. }
  183. var addDefaultTransformUnit = function(prop, val, intialVal) {
  184. if (getUnit(val)) return val;
  185. if (prop.indexOf('translate') > -1) return getUnit(intialVal) ? val + getUnit(intialVal) : val + 'px';
  186. if (prop.indexOf('rotate') > -1 || prop.indexOf('skew') > -1) return val + 'deg';
  187. return val;
  188. }
  189. // Values
  190. var getAnimationType = function(el, prop) {
  191. if ((is.node(el) || is.svg(el)) && arrayContains(validTransforms, prop)) return 'transform';
  192. if ((is.node(el) || is.svg(el)) && (prop !== 'transform' && getCSSValue(el, prop))) return 'css';
  193. if ((is.node(el) || is.svg(el)) && (el.getAttribute(prop) || el[prop])) return 'attribute';
  194. if (!is.null(el[prop]) && !is.undef(el[prop])) return 'object';
  195. }
  196. var getCSSValue = function(el, prop) {
  197. return getComputedStyle(el).getPropertyValue(stringToHyphens(prop));
  198. }
  199. var getTransformValue = function(el, prop) {
  200. var defaultVal = prop.indexOf('scale') > -1 ? 1 : 0;
  201. var str = el.style.transform;
  202. if (!str) return defaultVal;
  203. var rgx = /(\w+)\((.+?)\)/g;
  204. var match = [];
  205. var props = [];
  206. var values = [];
  207. while (match = rgx.exec(str)) {
  208. props.push(match[1]);
  209. values.push(match[2]);
  210. }
  211. var val = values.filter(function(f, i) { return props[i] === prop; });
  212. return val.length ? val[0] : defaultVal;
  213. }
  214. var getInitialTargetValue = function(target, prop) {
  215. switch (getAnimationType(target, prop)) {
  216. case 'transform': return getTransformValue(target, prop);
  217. case 'css': return getCSSValue(target, prop);
  218. case 'attribute': return target.getAttribute(prop);
  219. }
  220. return target[prop] || 0;
  221. }
  222. var getValidValue = function(values, val, originalCSS) {
  223. if (is.color(val)) return colorToRgb(val);
  224. if (getUnit(val)) return val;
  225. var unit = getUnit(values.to) ? getUnit(values.to) : getUnit(values.from);
  226. if (!unit && originalCSS) unit = getUnit(originalCSS);
  227. return unit ? val + unit : val;
  228. }
  229. var decomposeValue = function(val) {
  230. var rgx = /-?\d*\.?\d+/g;
  231. return {
  232. original: val,
  233. numbers: numberToString(val).match(rgx) ? numberToString(val).match(rgx).map(Number) : [0],
  234. strings: numberToString(val).split(rgx)
  235. }
  236. }
  237. var recomposeValue = function(numbers, strings, initialStrings) {
  238. return strings.reduce(function(a, b, i) {
  239. var b = (b ? b : initialStrings[i - 1]);
  240. return a + numbers[i - 1] + b;
  241. });
  242. }
  243. // Animatables
  244. var getAnimatables = function(targets) {
  245. var targets = targets ? (flattenArray(is.array(targets) ? targets.map(toArray) : toArray(targets))) : [];
  246. return targets.map(function(t, i) {
  247. return { target: t, id: i };
  248. });
  249. }
  250. // Properties
  251. var getProperties = function(params, settings) {
  252. var props = [];
  253. for (var p in params) {
  254. if (!defaultSettings.hasOwnProperty(p) && p !== 'targets') {
  255. var prop = is.object(params[p]) ? cloneObject(params[p]) : {value: params[p]};
  256. prop.name = p;
  257. props.push(mergeObjects(prop, settings));
  258. }
  259. }
  260. return props;
  261. }
  262. var getPropertiesValues = function(target, prop, value, i) {
  263. var values = toArray( is.func(value) ? value(target, i) : value);
  264. return {
  265. from: (values.length > 1) ? values[0] : getInitialTargetValue(target, prop),
  266. to: (values.length > 1) ? values[1] : values[0]
  267. }
  268. }
  269. var getTweenValues = function(prop, values, type, target) {
  270. var valid = {};
  271. if (type === 'transform') {
  272. valid.from = prop + '(' + addDefaultTransformUnit(prop, values.from, values.to) + ')';
  273. valid.to = prop + '(' + addDefaultTransformUnit(prop, values.to) + ')';
  274. } else {
  275. var originalCSS = (type === 'css') ? getCSSValue(target, prop) : undefined;
  276. valid.from = getValidValue(values, values.from, originalCSS);
  277. valid.to = getValidValue(values, values.to, originalCSS);
  278. }
  279. return { from: decomposeValue(valid.from), to: decomposeValue(valid.to) };
  280. }
  281. var getTweensProps = function(animatables, props) {
  282. var tweensProps = [];
  283. animatables.forEach(function(animatable, i) {
  284. var target = animatable.target;
  285. return props.forEach(function(prop) {
  286. var animType = getAnimationType(target, prop.name);
  287. if (animType) {
  288. var values = getPropertiesValues(target, prop.name, prop.value, i);
  289. var tween = cloneObject(prop);
  290. tween.animatables = animatable;
  291. tween.type = animType;
  292. tween.from = getTweenValues(prop.name, values, tween.type, target).from;
  293. tween.to = getTweenValues(prop.name, values, tween.type, target).to;
  294. tween.round = (is.color(values.from) || tween.round) ? 1 : 0;
  295. tween.delay = (is.func(tween.delay) ? tween.delay(target, i, animatables.length) : tween.delay) / animation.speed;
  296. tween.duration = (is.func(tween.duration) ? tween.duration(target, i, animatables.length) : tween.duration) / animation.speed;
  297. tweensProps.push(tween);
  298. }
  299. });
  300. });
  301. return tweensProps;
  302. }
  303. // Tweens
  304. var getTweens = function(animatables, props) {
  305. var tweensProps = getTweensProps(animatables, props);
  306. var splittedProps = groupArrayByProps(tweensProps, ['name', 'from', 'to', 'delay', 'duration']);
  307. return splittedProps.map(function(tweenProps) {
  308. var tween = cloneObject(tweenProps[0]);
  309. tween.animatables = tweenProps.map(function(p) { return p.animatables });
  310. tween.totalDuration = tween.delay + tween.duration;
  311. return tween;
  312. });
  313. }
  314. var reverseTweens = function(anim, delays) {
  315. anim.tweens.forEach(function(tween) {
  316. var toVal = tween.to;
  317. var fromVal = tween.from;
  318. var delayVal = anim.duration - (tween.delay + tween.duration);
  319. tween.from = toVal;
  320. tween.to = fromVal;
  321. if (delays) tween.delay = delayVal;
  322. });
  323. anim.reversed = anim.reversed ? false : true;
  324. }
  325. // will-change
  326. var getWillChange = function(anim) {
  327. var props = [];
  328. var els = [];
  329. anim.tweens.forEach(function(tween) {
  330. if (tween.type === 'css' || tween.type === 'transform' ) {
  331. props.push(tween.type === 'css' ? stringToHyphens(tween.name) : 'transform');
  332. tween.animatables.forEach(function(animatable) { els.push(animatable.target); });
  333. }
  334. });
  335. return {
  336. properties: removeArrayDuplicates(props).join(', '),
  337. elements: removeArrayDuplicates(els)
  338. }
  339. }
  340. var setWillChange = function(anim) {
  341. var willChange = getWillChange(anim);
  342. willChange.elements.forEach(function(element) {
  343. element.style.willChange = willChange.properties;
  344. });
  345. }
  346. var removeWillChange = function(anim) {
  347. var willChange = getWillChange(anim);
  348. willChange.elements.forEach(function(element) {
  349. element.style.removeProperty('will-change');
  350. });
  351. }
  352. /* Svg path */
  353. var getPathProps = function(path) {
  354. var el = is.string(path) ? selectString(path)[0] : path;
  355. return {
  356. path: el,
  357. value: el.getTotalLength()
  358. }
  359. }
  360. var snapProgressToPath = function(tween, progress) {
  361. var pathEl = tween.path;
  362. var pathProgress = tween.value * progress;
  363. var point = function(offset) {
  364. var o = offset || 0;
  365. var p = progress > 1 ? tween.value + o : pathProgress + o;
  366. return pathEl.getPointAtLength(p);
  367. }
  368. var p = point();
  369. var p0 = point(-1);
  370. var p1 = point(+1);
  371. switch (tween.name) {
  372. case 'translateX': return p.x;
  373. case 'translateY': return p.y;
  374. case 'rotate': return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI;
  375. }
  376. }
  377. // Progress
  378. var getTweenProgress = function(tween, time) {
  379. var elapsed = Math.min(Math.max(time - tween.delay, 0), tween.duration);
  380. var percent = elapsed / tween.duration;
  381. var progress = tween.to.numbers.map(function(number, p) {
  382. var start = tween.from.numbers[p];
  383. var eased = easings[tween.easing](percent, tween.elasticity);
  384. var val = tween.path ? snapProgressToPath(tween, eased) : start + eased * (number - start);
  385. val = tween.round ? Math.round(val * tween.round) / tween.round : val;
  386. return val;
  387. });
  388. return recomposeValue(progress, tween.to.strings, tween.from.strings);
  389. }
  390. var setAnimationProgress = function(anim, time) {
  391. var transforms = undefined;
  392. anim.time = Math.min(time, anim.duration);
  393. anim.progress = (anim.time / anim.duration) * 100;
  394. anim.tweens.forEach(function(tween) {
  395. tween.currentValue = getTweenProgress(tween, time);
  396. var progress = tween.currentValue;
  397. tween.animatables.forEach(function(animatable) {
  398. var id = animatable.id;
  399. switch (tween.type) {
  400. case 'css': animatable.target.style[tween.name] = progress; break;
  401. case 'attribute': animatable.target.setAttribute(tween.name, progress); break;
  402. case 'object': animatable.target[tween.name] = progress; break;
  403. case 'transform':
  404. if (!transforms) transforms = {};
  405. if (!transforms[id]) transforms[id] = [];
  406. transforms[id].push(progress);
  407. break;
  408. }
  409. });
  410. });
  411. if (transforms) for (var t in transforms) anim.animatables[t].target.style.transform = transforms[t].join(' ');
  412. if (anim.settings.update) anim.settings.update(anim);
  413. }
  414. // Animation
  415. var createAnimation = function(params) {
  416. var anim = {};
  417. anim.animatables = getAnimatables(params.targets);
  418. anim.settings = mergeObjects(params, defaultSettings);
  419. anim.properties = getProperties(params, anim.settings);
  420. anim.tweens = getTweens(anim.animatables, anim.properties);
  421. anim.duration = anim.tweens.length ? Math.max.apply(Math, anim.tweens.map(function(tween){ return tween.totalDuration; })) : params.duration / animation.speed;
  422. anim.time = 0;
  423. anim.progress = 0;
  424. anim.running = false;
  425. anim.ended = false;
  426. return anim;
  427. }
  428. // Public
  429. var animations = [];
  430. var animation = function(params) {
  431. var anim = createAnimation(params);
  432. var time = {};
  433. time.tick = function() {
  434. if (anim.running) {
  435. anim.ended = false;
  436. time.now = +new Date();
  437. time.current = time.last + time.now - time.start;
  438. setAnimationProgress(anim, time.current);
  439. var s = anim.settings;
  440. if (s.begin && time.current >= s.delay) { s.begin(anim); s.begin = undefined; };
  441. if (time.current >= anim.duration) {
  442. if (s.loop) {
  443. time.start = +new Date();
  444. if (s.direction === 'alternate') reverseTweens(anim, true);
  445. if (is.number(s.loop)) s.loop--;
  446. time.raf = requestAnimationFrame(time.tick);
  447. } else {
  448. anim.ended = true;
  449. if (s.complete) s.complete(anim);
  450. anim.pause();
  451. }
  452. time.last = 0;
  453. } else {
  454. time.raf = requestAnimationFrame(time.tick);
  455. }
  456. }
  457. }
  458. anim.seek = function(progress) {
  459. var time = (progress / 100) * anim.duration;
  460. setAnimationProgress(anim, time);
  461. }
  462. anim.pause = function() {
  463. anim.running = false;
  464. cancelAnimationFrame(time.raf);
  465. removeWillChange(anim);
  466. var i = animations.indexOf(anim);
  467. if (i > -1) animations.splice(i, 1);
  468. }
  469. anim.play = function(params) {
  470. if (params) anim = mergeObjects(createAnimation(mergeObjects(params, anim.settings)), anim);
  471. anim.pause();
  472. anim.running = true;
  473. time.start = +new Date();
  474. time.last = anim.ended ? 0 : anim.time;
  475. var s = anim.settings;
  476. if (s.direction === 'reverse') reverseTweens(anim);
  477. if (s.direction === 'alternate' && !s.loop) s.loop = 1;
  478. setWillChange(anim);
  479. animations.push(anim);
  480. time.raf = requestAnimationFrame(time.tick);
  481. }
  482. anim.restart = function() {
  483. if (anim.reversed) reverseTweens(anim);
  484. anim.pause();
  485. anim.seek(0);
  486. anim.play();
  487. }
  488. if (anim.settings.autoplay) anim.play();
  489. return anim;
  490. }
  491. // Remove on one or multiple targets from all active animations.
  492. var remove = function(elements) {
  493. var targets = flattenArray(is.array(elements) ? elements.map(toArray) : toArray(elements));
  494. for (var i = animations.length-1; i >= 0; i--) {
  495. var animation = animations[i];
  496. for (var t = animation.tweens.length-1; t >= 0; t--) {
  497. var tween = animation.tweens[t];
  498. for (var a = tween.animatables.length-1; a >= 0; a--) {
  499. if (arrayContains(targets, tween.animatables[a].target)) {
  500. tween.animatables.splice(a, 1);
  501. if (!tween.animatables.length) animation.tweens.splice(t, 1);
  502. if (!animation.tweens.length) animation.pause();
  503. }
  504. }
  505. }
  506. }
  507. }
  508. animation.speed = 1;
  509. animation.list = animations;
  510. animation.remove = remove;
  511. animation.easings = easings;
  512. animation.getValue = getInitialTargetValue;
  513. animation.path = getPathProps;
  514. animation.random = random;
  515. return animation;
  516. })();