API Docs for: 3.17.2
Show:

File: anim/js/anim.js

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