Version 3.18.1
Show:

File: anim/js/anim.js

  1. /**
  2. * The Animation Utility provides an API for creating advanced transitions.
  3. * @module anim
  4. */
  5. /**
  6. * Provides the base Anim class, for animating numeric properties.
  7. *
  8. * @module anim
  9. * @submodule anim-base
  10. */
  11. /**
  12. * A class for constructing animation instances.
  13. * @class Anim
  14. * @for Anim
  15. * @constructor
  16. * @extends Base
  17. */
  18. var RUNNING = 'running',
  19. START_TIME = 'startTime',
  20. ELAPSED_TIME = 'elapsedTime',
  21. /**
  22. * @for Anim
  23. * @event start
  24. * @description fires when an animation begins.
  25. * @param {Event} ev The start event.
  26. * @type Event.Custom
  27. */
  28. START = 'start',
  29. /**
  30. * @event tween
  31. * @description fires every frame of the animation.
  32. * @param {Event} ev The tween event.
  33. * @type Event.Custom
  34. */
  35. TWEEN = 'tween',
  36. /**
  37. * @event end
  38. * @description fires after the animation completes.
  39. * @param {Event} ev The end event.
  40. * @type Event.Custom
  41. */
  42. END = 'end',
  43. NODE = 'node',
  44. PAUSED = 'paused',
  45. REVERSE = 'reverse', // TODO: cleanup
  46. ITERATION_COUNT = 'iterationCount',
  47. NUM = Number;
  48. var _running = {},
  49. _timer;
  50. Y.Anim = function() {
  51. Y.Anim.superclass.constructor.apply(this, arguments);
  52. Y.Anim._instances[Y.stamp(this)] = this;
  53. };
  54. Y.Anim.NAME = 'anim';
  55. Y.Anim._instances = {};
  56. /**
  57. * Regex of properties that should use the default unit.
  58. *
  59. * @property RE_DEFAULT_UNIT
  60. * @static
  61. */
  62. Y.Anim.RE_DEFAULT_UNIT = /^width|height|top|right|bottom|left|margin.*|padding.*|border.*$/i;
  63. /**
  64. * The default unit to use with properties that pass the RE_DEFAULT_UNIT test.
  65. *
  66. * @property DEFAULT_UNIT
  67. * @static
  68. */
  69. Y.Anim.DEFAULT_UNIT = 'px';
  70. Y.Anim.DEFAULT_EASING = function (t, b, c, d) {
  71. return c * t / d + b; // linear easing
  72. };
  73. /**
  74. * Time in milliseconds passed to setInterval for frame processing
  75. *
  76. * @property intervalTime
  77. * @default 20
  78. * @static
  79. */
  80. Y.Anim._intervalTime = 20;
  81. /**
  82. * Bucket for custom getters and setters
  83. *
  84. * @property behaviors
  85. * @static
  86. */
  87. Y.Anim.behaviors = {
  88. left: {
  89. get: function(anim, attr) {
  90. return anim._getOffset(attr);
  91. }
  92. }
  93. };
  94. Y.Anim.behaviors.top = Y.Anim.behaviors.left;
  95. /**
  96. * The default setter to use when setting object properties.
  97. *
  98. * @property DEFAULT_SETTER
  99. * @static
  100. */
  101. Y.Anim.DEFAULT_SETTER = function(anim, att, from, to, elapsed, duration, fn, unit) {
  102. var node = anim._node,
  103. domNode = node._node,
  104. val = fn(elapsed, NUM(from), NUM(to) - NUM(from), duration);
  105. if (domNode) {
  106. if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
  107. unit = unit || '';
  108. node.setStyle(att, val + unit);
  109. } else if ('attributes' in domNode && att in domNode.attributes) {
  110. node.setAttribute(att, val);
  111. } else if (att in domNode) {
  112. domNode[att] = val;
  113. }
  114. } else if (node.set) {
  115. node.set(att, val);
  116. } else if (att in node) {
  117. node[att] = val;
  118. }
  119. };
  120. /**
  121. * The default getter to use when getting object properties.
  122. *
  123. * @property DEFAULT_GETTER
  124. * @static
  125. */
  126. Y.Anim.DEFAULT_GETTER = function(anim, att) {
  127. var node = anim._node,
  128. domNode = node._node,
  129. val = '';
  130. if (domNode) {
  131. if ('style' in domNode && (att in domNode.style || att in Y.DOM.CUSTOM_STYLES)) {
  132. val = node.getComputedStyle(att);
  133. } else if ('attributes' in domNode && att in domNode.attributes) {
  134. val = node.getAttribute(att);
  135. } else if (att in domNode) {
  136. val = domNode[att];
  137. }
  138. } else if (node.get) {
  139. val = node.get(att);
  140. } else if (att in node) {
  141. val = node[att];
  142. }
  143. return val;
  144. };
  145. Y.Anim.ATTRS = {
  146. /**
  147. * The object to be animated.
  148. * @attribute node
  149. * @type Node
  150. */
  151. node: {
  152. setter: function(node) {
  153. if (node) {
  154. if (typeof node === 'string' || node.nodeType) {
  155. node = Y.one(node);
  156. }
  157. }
  158. this._node = node;
  159. if (!node) {
  160. Y.log(node + ' is not a valid node', 'warn', 'Anim');
  161. }
  162. return node;
  163. }
  164. },
  165. /**
  166. * The length of the animation. Defaults to "1" (second).
  167. * @attribute duration
  168. * @type NUM
  169. */
  170. duration: {
  171. value: 1
  172. },
  173. /**
  174. * The method that will provide values to the attribute(s) during the animation.
  175. * Defaults to "Easing.easeNone".
  176. * @attribute easing
  177. * @type Function
  178. */
  179. easing: {
  180. value: Y.Anim.DEFAULT_EASING,
  181. setter: function(val) {
  182. if (typeof val === 'string' && Y.Easing) {
  183. return Y.Easing[val];
  184. }
  185. }
  186. },
  187. /**
  188. * The starting values for the animated properties.
  189. *
  190. * Fields may be strings, numbers, or functions.
  191. * If a function is used, the return value becomes the from value.
  192. * If no from value is specified, the DEFAULT_GETTER will be used.
  193. * Supports any unit, provided it matches the "to" (or default)
  194. * unit (e.g. `{width: '10em', color: 'rgb(0, 0, 0)', borderColor: '#ccc'}`).
  195. *
  196. * If using the default ('px' for length-based units), the unit may be omitted
  197. * (e.g. `{width: 100}, borderColor: 'ccc'}`, which defaults to pixels
  198. * and hex, respectively).
  199. *
  200. * @attribute from
  201. * @type Object
  202. */
  203. from: {},
  204. /**
  205. * The ending values for the animated properties.
  206. *
  207. * Fields may be strings, numbers, or functions.
  208. * Supports any unit, provided it matches the "from" (or default)
  209. * unit (e.g. `{width: '50%', color: 'red', borderColor: '#ccc'}`).
  210. *
  211. * If using the default ('px' for length-based units), the unit may be omitted
  212. * (e.g. `{width: 100, borderColor: 'ccc'}`, which defaults to pixels
  213. * and hex, respectively).
  214. *
  215. * @attribute to
  216. * @type Object
  217. */
  218. to: {},
  219. /**
  220. * Date stamp for the first frame of the animation.
  221. * @attribute startTime
  222. * @type Int
  223. * @default 0
  224. * @readOnly
  225. */
  226. startTime: {
  227. value: 0,
  228. readOnly: true
  229. },
  230. /**
  231. * Current time the animation has been running.
  232. * @attribute elapsedTime
  233. * @type Int
  234. * @default 0
  235. * @readOnly
  236. */
  237. elapsedTime: {
  238. value: 0,
  239. readOnly: true
  240. },
  241. /**
  242. * Whether or not the animation is currently running.
  243. * @attribute running
  244. * @type Boolean
  245. * @default false
  246. * @readOnly
  247. */
  248. running: {
  249. getter: function() {
  250. return !!_running[Y.stamp(this)];
  251. },
  252. value: false,
  253. readOnly: true
  254. },
  255. /**
  256. * The number of times the animation should run
  257. * @attribute iterations
  258. * @type Int
  259. * @default 1
  260. */
  261. iterations: {
  262. value: 1
  263. },
  264. /**
  265. * The number of iterations that have occurred.
  266. * Resets when an animation ends (reaches iteration count or stop() called).
  267. * @attribute iterationCount
  268. * @type Int
  269. * @default 0
  270. * @readOnly
  271. */
  272. iterationCount: {
  273. value: 0,
  274. readOnly: true
  275. },
  276. /**
  277. * How iterations of the animation should behave.
  278. * Possible values are "normal" and "alternate".
  279. * Normal will repeat the animation, alternate will reverse on every other pass.
  280. *
  281. * @attribute direction
  282. * @type String
  283. * @default "normal"
  284. */
  285. direction: {
  286. value: 'normal' // | alternate (fwd on odd, rev on even per spec)
  287. },
  288. /**
  289. * Whether or not the animation is currently paused.
  290. * @attribute paused
  291. * @type Boolean
  292. * @default false
  293. * @readOnly
  294. */
  295. paused: {
  296. readOnly: true,
  297. value: false
  298. },
  299. /**
  300. * If true, the `from` and `to` attributes are swapped,
  301. * and the animation is then run starting from `from`.
  302. * @attribute reverse
  303. * @type Boolean
  304. * @default false
  305. */
  306. reverse: {
  307. value: false
  308. }
  309. };
  310. /**
  311. * Runs all animation instances.
  312. * @method run
  313. * @static
  314. */
  315. Y.Anim.run = function() {
  316. var instances = Y.Anim._instances,
  317. i;
  318. for (i in instances) {
  319. if (instances[i].run) {
  320. instances[i].run();
  321. }
  322. }
  323. };
  324. /**
  325. * Pauses all animation instances.
  326. * @method pause
  327. * @static
  328. */
  329. Y.Anim.pause = function() {
  330. for (var i in _running) { // stop timer if nothing running
  331. if (_running[i].pause) {
  332. _running[i].pause();
  333. }
  334. }
  335. Y.Anim._stopTimer();
  336. };
  337. /**
  338. * Stops all animation instances.
  339. * @method stop
  340. * @static
  341. */
  342. Y.Anim.stop = function() {
  343. for (var i in _running) { // stop timer if nothing running
  344. if (_running[i].stop) {
  345. _running[i].stop();
  346. }
  347. }
  348. Y.Anim._stopTimer();
  349. };
  350. Y.Anim._startTimer = function() {
  351. if (!_timer) {
  352. _timer = setInterval(Y.Anim._runFrame, Y.Anim._intervalTime);
  353. }
  354. };
  355. Y.Anim._stopTimer = function() {
  356. clearInterval(_timer);
  357. _timer = 0;
  358. };
  359. /**
  360. * Called per Interval to handle each animation frame.
  361. * @method _runFrame
  362. * @private
  363. * @static
  364. */
  365. Y.Anim._runFrame = function() {
  366. var done = true,
  367. anim;
  368. for (anim in _running) {
  369. if (_running[anim]._runFrame) {
  370. done = false;
  371. _running[anim]._runFrame();
  372. }
  373. }
  374. if (done) {
  375. Y.Anim._stopTimer();
  376. }
  377. };
  378. Y.Anim.RE_UNITS = /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/;
  379. var proto = {
  380. /**
  381. * Starts or resumes an animation.
  382. * @method run
  383. * @chainable
  384. */
  385. run: function() {
  386. if (this.get(PAUSED)) {
  387. this._resume();
  388. } else if (!this.get(RUNNING)) {
  389. this._start();
  390. }
  391. return this;
  392. },
  393. /**
  394. * Pauses the animation and
  395. * freezes it in its current state and time.
  396. * Calling run() will continue where it left off.
  397. * @method pause
  398. * @chainable
  399. */
  400. pause: function() {
  401. if (this.get(RUNNING)) {
  402. this._pause();
  403. }
  404. return this;
  405. },
  406. /**
  407. * Stops the animation and resets its time.
  408. * @method stop
  409. * @param {Boolean} finish If true, the animation will move to the last frame
  410. * @chainable
  411. */
  412. stop: function(finish) {
  413. if (this.get(RUNNING) || this.get(PAUSED)) {
  414. this._end(finish);
  415. }
  416. return this;
  417. },
  418. _added: false,
  419. _start: function() {
  420. this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
  421. this._actualFrames = 0;
  422. if (!this.get(PAUSED)) {
  423. this._initAnimAttr();
  424. }
  425. _running[Y.stamp(this)] = this;
  426. Y.Anim._startTimer();
  427. this.fire(START);
  428. },
  429. _pause: function() {
  430. this._set(START_TIME, null);
  431. this._set(PAUSED, true);
  432. delete _running[Y.stamp(this)];
  433. /**
  434. * @event pause
  435. * @description fires when an animation is paused.
  436. * @param {Event} ev The pause event.
  437. * @type Event.Custom
  438. */
  439. this.fire('pause');
  440. },
  441. _resume: function() {
  442. this._set(PAUSED, false);
  443. _running[Y.stamp(this)] = this;
  444. this._set(START_TIME, new Date() - this.get(ELAPSED_TIME));
  445. Y.Anim._startTimer();
  446. /**
  447. * @event resume
  448. * @description fires when an animation is resumed (run from pause).
  449. * @param {Event} ev The pause event.
  450. * @type Event.Custom
  451. */
  452. this.fire('resume');
  453. },
  454. _end: function(finish) {
  455. var duration = this.get('duration') * 1000;
  456. if (finish) { // jump to last frame
  457. this._runAttrs(duration, duration, this.get(REVERSE));
  458. }
  459. this._set(START_TIME, null);
  460. this._set(ELAPSED_TIME, 0);
  461. this._set(PAUSED, false);
  462. delete _running[Y.stamp(this)];
  463. this.fire(END, {elapsed: this.get(ELAPSED_TIME)});
  464. },
  465. _runFrame: function() {
  466. var d = this._runtimeAttr.duration,
  467. t = new Date() - this.get(START_TIME),
  468. reverse = this.get(REVERSE),
  469. done = (t >= d);
  470. this._runAttrs(t, d, reverse);
  471. this._actualFrames += 1;
  472. this._set(ELAPSED_TIME, t);
  473. this.fire(TWEEN);
  474. if (done) {
  475. this._lastFrame();
  476. }
  477. },
  478. _runAttrs: function(t, d, reverse) {
  479. var attr = this._runtimeAttr,
  480. customAttr = Y.Anim.behaviors,
  481. easing = attr.easing,
  482. lastFrame = d,
  483. done = false,
  484. attribute,
  485. setter,
  486. i;
  487. if (t >= d) {
  488. done = true;
  489. }
  490. if (reverse) {
  491. t = d - t;
  492. lastFrame = 0;
  493. }
  494. for (i in attr) {
  495. if (attr[i].to) {
  496. attribute = attr[i];
  497. setter = (i in customAttr && 'set' in customAttr[i]) ?
  498. customAttr[i].set : Y.Anim.DEFAULT_SETTER;
  499. if (!done) {
  500. setter(this, i, attribute.from, attribute.to, t, d, easing, attribute.unit);
  501. } else {
  502. setter(this, i, attribute.from, attribute.to, lastFrame, d, easing, attribute.unit);
  503. }
  504. }
  505. }
  506. },
  507. _lastFrame: function() {
  508. var iter = this.get('iterations'),
  509. iterCount = this.get(ITERATION_COUNT);
  510. iterCount += 1;
  511. if (iter === 'infinite' || iterCount < iter) {
  512. if (this.get('direction') === 'alternate') {
  513. this.set(REVERSE, !this.get(REVERSE)); // flip it
  514. }
  515. /**
  516. * @event iteration
  517. * @description fires when an animation begins an iteration.
  518. * @param {Event} ev The iteration event.
  519. * @type Event.Custom
  520. */
  521. this.fire('iteration');
  522. } else {
  523. iterCount = 0;
  524. this._end();
  525. }
  526. this._set(START_TIME, new Date());
  527. this._set(ITERATION_COUNT, iterCount);
  528. },
  529. _initAnimAttr: function() {
  530. var from = this.get('from') || {},
  531. to = this.get('to') || {},
  532. attr = {
  533. duration: this.get('duration') * 1000,
  534. easing: this.get('easing')
  535. },
  536. customAttr = Y.Anim.behaviors,
  537. node = this.get(NODE), // implicit attr init
  538. unit, begin, end;
  539. Y.each(to, function(val, name) {
  540. if (typeof val === 'function') {
  541. val = val.call(this, node);
  542. }
  543. begin = from[name];
  544. if (begin === undefined) {
  545. begin = (name in customAttr && 'get' in customAttr[name]) ?
  546. customAttr[name].get(this, name) : Y.Anim.DEFAULT_GETTER(this, name);
  547. } else if (typeof begin === 'function') {
  548. begin = begin.call(this, node);
  549. }
  550. var mFrom = Y.Anim.RE_UNITS.exec(begin),
  551. mTo = Y.Anim.RE_UNITS.exec(val);
  552. begin = mFrom ? mFrom[1] : begin;
  553. end = mTo ? mTo[1] : val;
  554. unit = mTo ? mTo[2] : mFrom ? mFrom[2] : ''; // one might be zero TODO: mixed units
  555. if (!unit && Y.Anim.RE_DEFAULT_UNIT.test(name)) {
  556. unit = Y.Anim.DEFAULT_UNIT;
  557. }
  558. if (!begin || !end) {
  559. Y.error('invalid "from" or "to" for "' + name + '"', 'Anim');
  560. return;
  561. }
  562. attr[name] = {
  563. from: Y.Lang.isObject(begin) ? Y.clone(begin) : begin,
  564. to: end,
  565. unit: unit
  566. };
  567. }, this);
  568. this._runtimeAttr = attr;
  569. },
  570. // TODO: move to computedStyle? (browsers dont agree on default computed offsets)
  571. _getOffset: function(attr) {
  572. var node = this._node,
  573. val = node.getComputedStyle(attr),
  574. get = (attr === 'left') ? 'getX': 'getY',
  575. set = (attr === 'left') ? 'setX': 'setY',
  576. position;
  577. if (val === 'auto') {
  578. position = node.getStyle('position');
  579. if (position === 'absolute' || position === 'fixed') {
  580. val = node[get]();
  581. node[set](val);
  582. } else {
  583. val = 0;
  584. }
  585. }
  586. return val;
  587. },
  588. destructor: function() {
  589. delete Y.Anim._instances[Y.stamp(this)];
  590. }
  591. };
  592. Y.extend(Y.Anim, Y.Base, proto);