API Docs for: 3.10.3
Show:

File: transition/js/transition-native.js

  1. /**
  2. * Provides the transition method for Node.
  3. * Transition has no API of its own, but adds the transition method to Node.
  4. *
  5. * @module transition
  6. * @requires node-style
  7. */
  8.  
  9. var CAMEL_VENDOR_PREFIX = '',
  10. VENDOR_PREFIX = '',
  11. DOCUMENT = Y.config.doc,
  12. DOCUMENT_ELEMENT = 'documentElement',
  13. DOCUMENT_STYLE = DOCUMENT[DOCUMENT_ELEMENT].style,
  14. TRANSITION_CAMEL = 'transition',
  15. TRANSITION_PROPERTY_CAMEL = 'transitionProperty',
  16. TRANSITION_PROPERTY,
  17. TRANSITION_DURATION,
  18. TRANSITION_TIMING_FUNCTION,
  19. TRANSITION_DELAY,
  20. TRANSITION_END,
  21. ON_TRANSITION_END,
  22.  
  23. EMPTY_OBJ = {},
  24.  
  25. VENDORS = [
  26. 'Webkit',
  27. 'Moz'
  28. ],
  29.  
  30. VENDOR_TRANSITION_END = {
  31. Webkit: 'webkitTransitionEnd'
  32. },
  33.  
  34. /**
  35. * A class for constructing transition instances.
  36. * Adds the "transition" method to Node.
  37. * @class Transition
  38. * @constructor
  39. */
  40.  
  41. Transition = function() {
  42. this.init.apply(this, arguments);
  43. };
  44.  
  45. // One off handling of transform-prefixing.
  46. Transition._TRANSFORM = 'transform';
  47.  
  48. Transition._toCamel = function(property) {
  49. property = property.replace(/-([a-z])/gi, function(m0, m1) {
  50. return m1.toUpperCase();
  51. });
  52.  
  53. return property;
  54. };
  55.  
  56. Transition._toHyphen = function(property) {
  57. property = property.replace(/([A-Z]?)([a-z]+)([A-Z]?)/g, function(m0, m1, m2, m3) {
  58. var str = ((m1) ? '-' + m1.toLowerCase() : '') + m2;
  59.  
  60. if (m3) {
  61. str += '-' + m3.toLowerCase();
  62. }
  63.  
  64. return str;
  65. });
  66.  
  67. return property;
  68. };
  69.  
  70. Transition.SHOW_TRANSITION = 'fadeIn';
  71. Transition.HIDE_TRANSITION = 'fadeOut';
  72.  
  73. Transition.useNative = false;
  74.  
  75. // Map transition properties to vendor-specific versions.
  76. if ('transition' in DOCUMENT_STYLE
  77. && 'transitionProperty' in DOCUMENT_STYLE
  78. && 'transitionDuration' in DOCUMENT_STYLE
  79. && 'transitionTimingFunction' in DOCUMENT_STYLE
  80. && 'transitionDelay' in DOCUMENT_STYLE) {
  81. Transition.useNative = true;
  82. Transition.supported = true; // TODO: remove
  83. } else {
  84. Y.Array.each(VENDORS, function(val) { // then vendor specific
  85. var property = val + 'Transition';
  86. if (property in DOCUMENT[DOCUMENT_ELEMENT].style) {
  87. CAMEL_VENDOR_PREFIX = val;
  88. VENDOR_PREFIX = Transition._toHyphen(val) + '-';
  89.  
  90. Transition.useNative = true;
  91. Transition.supported = true; // TODO: remove
  92. Transition._VENDOR_PREFIX = val;
  93. }
  94. });
  95. }
  96.  
  97. // Map transform property to vendor-specific versions.
  98. // One-off required for cssText injection.
  99. if (typeof DOCUMENT_STYLE.transform === 'undefined') {
  100. Y.Array.each(VENDORS, function(val) { // then vendor specific
  101. var property = val + 'Transform';
  102. if (typeof DOCUMENT_STYLE[property] !== 'undefined') {
  103. Transition._TRANSFORM = property;
  104. }
  105. });
  106. }
  107.  
  108. if (CAMEL_VENDOR_PREFIX) {
  109. TRANSITION_CAMEL = CAMEL_VENDOR_PREFIX + 'Transition';
  110. TRANSITION_PROPERTY_CAMEL = CAMEL_VENDOR_PREFIX + 'TransitionProperty';
  111. }
  112.  
  113. TRANSITION_PROPERTY = VENDOR_PREFIX + 'transition-property';
  114. TRANSITION_DURATION = VENDOR_PREFIX + 'transition-duration';
  115. TRANSITION_TIMING_FUNCTION = VENDOR_PREFIX + 'transition-timing-function';
  116. TRANSITION_DELAY = VENDOR_PREFIX + 'transition-delay';
  117.  
  118. TRANSITION_END = 'transitionend';
  119. ON_TRANSITION_END = 'on' + CAMEL_VENDOR_PREFIX.toLowerCase() + 'transitionend';
  120. TRANSITION_END = VENDOR_TRANSITION_END[CAMEL_VENDOR_PREFIX] || TRANSITION_END;
  121.  
  122. Transition.fx = {};
  123. Transition.toggles = {};
  124.  
  125. Transition._hasEnd = {};
  126.  
  127. Transition._reKeywords = /^(?:node|duration|iterations|easing|delay|on|onstart|onend)$/i;
  128.  
  129. Y.Node.DOM_EVENTS[TRANSITION_END] = 1;
  130.  
  131. Transition.NAME = 'transition';
  132.  
  133. Transition.DEFAULT_EASING = 'ease';
  134. Transition.DEFAULT_DURATION = 0.5;
  135. Transition.DEFAULT_DELAY = 0;
  136.  
  137. Transition._nodeAttrs = {};
  138.  
  139. Transition.prototype = {
  140. constructor: Transition,
  141. init: function(node, config) {
  142. var anim = this;
  143. anim._node = node;
  144. if (!anim._running && config) {
  145. anim._config = config;
  146. node._transition = anim; // cache for reuse
  147.  
  148. anim._duration = ('duration' in config) ?
  149. config.duration: anim.constructor.DEFAULT_DURATION;
  150.  
  151. anim._delay = ('delay' in config) ?
  152. config.delay: anim.constructor.DEFAULT_DELAY;
  153.  
  154. anim._easing = config.easing || anim.constructor.DEFAULT_EASING;
  155. anim._count = 0; // track number of animated properties
  156. anim._running = false;
  157.  
  158. }
  159.  
  160. return anim;
  161. },
  162.  
  163. addProperty: function(prop, config) {
  164. var anim = this,
  165. node = this._node,
  166. uid = Y.stamp(node),
  167. nodeInstance = Y.one(node),
  168. attrs = Transition._nodeAttrs[uid],
  169. computed,
  170. compareVal,
  171. dur,
  172. attr,
  173. val;
  174.  
  175. if (!attrs) {
  176. attrs = Transition._nodeAttrs[uid] = {};
  177. }
  178.  
  179. attr = attrs[prop];
  180.  
  181. // might just be a value
  182. if (config && config.value !== undefined) {
  183. val = config.value;
  184. } else if (config !== undefined) {
  185. val = config;
  186. config = EMPTY_OBJ;
  187. }
  188.  
  189. if (typeof val === 'function') {
  190. val = val.call(nodeInstance, nodeInstance);
  191. }
  192.  
  193. if (attr && attr.transition) {
  194. // take control if another transition owns this property
  195. if (attr.transition !== anim) {
  196. attr.transition._count--; // remapping attr to this transition
  197. }
  198. }
  199.  
  200. anim._count++; // properties per transition
  201.  
  202. // make 0 async and fire events
  203. dur = ((typeof config.duration !== 'undefined') ? config.duration :
  204. anim._duration) || 0.0001;
  205.  
  206. attrs[prop] = {
  207. value: val,
  208. duration: dur,
  209. delay: (typeof config.delay !== 'undefined') ? config.delay :
  210. anim._delay,
  211.  
  212. easing: config.easing || anim._easing,
  213.  
  214. transition: anim
  215. };
  216.  
  217. // native end event doesnt fire when setting to same value
  218. // supplementing with timer
  219. // val may be a string or number (height: 0, etc), but computedStyle is always string
  220. computed = Y.DOM.getComputedStyle(node, prop);
  221. compareVal = (typeof val === 'string') ? computed : parseFloat(computed);
  222.  
  223. if (Transition.useNative && compareVal === val) {
  224. setTimeout(function() {
  225. anim._onNativeEnd.call(node, {
  226. propertyName: prop,
  227. elapsedTime: dur
  228. });
  229. }, dur * 1000);
  230. }
  231. },
  232.  
  233. removeProperty: function(prop) {
  234. var anim = this,
  235. attrs = Transition._nodeAttrs[Y.stamp(anim._node)];
  236.  
  237. if (attrs && attrs[prop]) {
  238. delete attrs[prop];
  239. anim._count--;
  240. }
  241.  
  242. },
  243.  
  244. initAttrs: function(config) {
  245. var attr,
  246. node = this._node;
  247.  
  248. if (config.transform && !config[Transition._TRANSFORM]) {
  249. config[Transition._TRANSFORM] = config.transform;
  250. delete config.transform; // TODO: copy
  251. }
  252.  
  253. for (attr in config) {
  254. if (config.hasOwnProperty(attr) && !Transition._reKeywords.test(attr)) {
  255. this.addProperty(attr, config[attr]);
  256.  
  257. // when size is auto or % webkit starts from zero instead of computed
  258. // (https://bugs.webkit.org/show_bug.cgi?id=16020)
  259. // TODO: selective set
  260. if (node.style[attr] === '') {
  261. Y.DOM.setStyle(node, attr, Y.DOM.getComputedStyle(node, attr));
  262. }
  263. }
  264. }
  265. },
  266.  
  267. /**
  268. * Starts or an animation.
  269. * @method run
  270. * @chainable
  271. * @private
  272. */
  273. run: function(callback) {
  274. var anim = this,
  275. node = anim._node,
  276. config = anim._config,
  277. data = {
  278. type: 'transition:start',
  279. config: config
  280. };
  281.  
  282.  
  283. if (!anim._running) {
  284. anim._running = true;
  285.  
  286. if (config.on && config.on.start) {
  287. config.on.start.call(Y.one(node), data);
  288. }
  289.  
  290. anim.initAttrs(anim._config);
  291.  
  292. anim._callback = callback;
  293. anim._start();
  294. }
  295.  
  296.  
  297. return anim;
  298. },
  299.  
  300. _start: function() {
  301. this._runNative();
  302. },
  303.  
  304. _prepDur: function(dur) {
  305. dur = parseFloat(dur) * 1000;
  306.  
  307. return dur + 'ms';
  308. },
  309.  
  310. _runNative: function() {
  311. var anim = this,
  312. node = anim._node,
  313. uid = Y.stamp(node),
  314. style = node.style,
  315. computed = node.ownerDocument.defaultView.getComputedStyle(node),
  316. attrs = Transition._nodeAttrs[uid],
  317. cssText = '',
  318. cssTransition = computed[Transition._toCamel(TRANSITION_PROPERTY)],
  319.  
  320. transitionText = TRANSITION_PROPERTY + ': ',
  321. duration = TRANSITION_DURATION + ': ',
  322. easing = TRANSITION_TIMING_FUNCTION + ': ',
  323. delay = TRANSITION_DELAY + ': ',
  324. hyphy,
  325. attr,
  326. name;
  327.  
  328. // preserve existing transitions
  329. if (cssTransition !== 'all') {
  330. transitionText += cssTransition + ',';
  331. duration += computed[Transition._toCamel(TRANSITION_DURATION)] + ',';
  332. easing += computed[Transition._toCamel(TRANSITION_TIMING_FUNCTION)] + ',';
  333. delay += computed[Transition._toCamel(TRANSITION_DELAY)] + ',';
  334.  
  335. }
  336.  
  337. // run transitions mapped to this instance
  338. for (name in attrs) {
  339. hyphy = Transition._toHyphen(name);
  340. attr = attrs[name];
  341. if ((attr = attrs[name]) && attr.transition === anim) {
  342. if (name in node.style) { // only native styles allowed
  343. duration += anim._prepDur(attr.duration) + ',';
  344. delay += anim._prepDur(attr.delay) + ',';
  345. easing += (attr.easing) + ',';
  346.  
  347. transitionText += hyphy + ',';
  348. cssText += hyphy + ': ' + attr.value + '; ';
  349. } else {
  350. this.removeProperty(name);
  351. }
  352. }
  353. }
  354.  
  355. transitionText = transitionText.replace(/,$/, ';');
  356. duration = duration.replace(/,$/, ';');
  357. easing = easing.replace(/,$/, ';');
  358. delay = delay.replace(/,$/, ';');
  359.  
  360. // only one native end event per node
  361. if (!Transition._hasEnd[uid]) {
  362. node.addEventListener(TRANSITION_END, anim._onNativeEnd, '');
  363. Transition._hasEnd[uid] = true;
  364.  
  365. }
  366.  
  367. style.cssText += transitionText + duration + easing + delay + cssText;
  368.  
  369. },
  370.  
  371. _end: function(elapsed) {
  372. var anim = this,
  373. node = anim._node,
  374. callback = anim._callback,
  375. config = anim._config,
  376. data = {
  377. type: 'transition:end',
  378. config: config,
  379. elapsedTime: elapsed
  380. },
  381.  
  382. nodeInstance = Y.one(node);
  383.  
  384. anim._running = false;
  385. anim._callback = null;
  386.  
  387. if (node) {
  388. if (config.on && config.on.end) {
  389. setTimeout(function() { // IE: allow previous update to finish
  390. config.on.end.call(nodeInstance, data);
  391.  
  392. // nested to ensure proper fire order
  393. if (callback) {
  394. callback.call(nodeInstance, data);
  395. }
  396.  
  397. }, 1);
  398. } else if (callback) {
  399. setTimeout(function() { // IE: allow previous update to finish
  400. callback.call(nodeInstance, data);
  401. }, 1);
  402. }
  403. }
  404.  
  405. },
  406.  
  407. _endNative: function(name) {
  408. var node = this._node,
  409. value = node.ownerDocument.defaultView.getComputedStyle(node, '')[Transition._toCamel(TRANSITION_PROPERTY)];
  410.  
  411. name = Transition._toHyphen(name);
  412. if (typeof value === 'string') {
  413. value = value.replace(new RegExp('(?:^|,\\s)' + name + ',?'), ',');
  414. value = value.replace(/^,|,$/, '');
  415. node.style[TRANSITION_CAMEL] = value;
  416. }
  417. },
  418.  
  419. _onNativeEnd: function(e) {
  420. var node = this,
  421. uid = Y.stamp(node),
  422. event = e,//e._event,
  423. name = Transition._toCamel(event.propertyName),
  424. elapsed = event.elapsedTime,
  425. attrs = Transition._nodeAttrs[uid],
  426. attr = attrs[name],
  427. anim = (attr) ? attr.transition : null,
  428. data,
  429. config;
  430.  
  431. if (anim) {
  432. anim.removeProperty(name);
  433. anim._endNative(name);
  434. config = anim._config[name];
  435.  
  436. data = {
  437. type: 'propertyEnd',
  438. propertyName: name,
  439. elapsedTime: elapsed,
  440. config: config
  441. };
  442.  
  443. if (config && config.on && config.on.end) {
  444. config.on.end.call(Y.one(node), data);
  445. }
  446.  
  447. if (anim._count <= 0) { // after propertyEnd fires
  448. anim._end(elapsed);
  449. node.style[TRANSITION_PROPERTY_CAMEL] = ''; // clean up style
  450. }
  451. }
  452. },
  453.  
  454. destroy: function() {
  455. var anim = this,
  456. node = anim._node;
  457.  
  458. if (node) {
  459. node.removeEventListener(TRANSITION_END, anim._onNativeEnd, false);
  460. anim._node = null;
  461. }
  462. }
  463. };
  464.  
  465. Y.Transition = Transition;
  466. Y.TransitionNative = Transition; // TODO: remove
  467.  
  468. /**
  469. * Animate one or more css properties to a given value. Requires the "transition" module.
  470. * <pre>example usage:
  471. * Y.one('#demo').transition({
  472. * duration: 1, // in seconds, default is 0.5
  473. * easing: 'ease-out', // default is 'ease'
  474. * delay: '1', // delay start for 1 second, default is 0
  475. *
  476. * height: '10px',
  477. * width: '10px',
  478. *
  479. * opacity: { // per property
  480. * value: 0,
  481. * duration: 2,
  482. * delay: 2,
  483. * easing: 'ease-in'
  484. * }
  485. * });
  486. * </pre>
  487. * @for Node
  488. * @method transition
  489. * @param {Object} config An object containing one or more style properties, a duration and an easing.
  490. * @param {Function} callback A function to run after the transition has completed.
  491. * @chainable
  492. */
  493. Y.Node.prototype.transition = function(name, config, callback) {
  494. var
  495. transitionAttrs = Transition._nodeAttrs[Y.stamp(this._node)],
  496. anim = (transitionAttrs) ? transitionAttrs.transition || null : null,
  497. fxConfig,
  498. prop;
  499.  
  500. if (typeof name === 'string') { // named effect, pull config from registry
  501. if (typeof config === 'function') {
  502. callback = config;
  503. config = null;
  504. }
  505.  
  506. fxConfig = Transition.fx[name];
  507.  
  508. if (config && typeof config !== 'boolean') {
  509. config = Y.clone(config);
  510.  
  511. for (prop in fxConfig) {
  512. if (fxConfig.hasOwnProperty(prop)) {
  513. if (! (prop in config)) {
  514. config[prop] = fxConfig[prop];
  515. }
  516. }
  517. }
  518. } else {
  519. config = fxConfig;
  520. }
  521.  
  522. } else { // name is a config, config is a callback or undefined
  523. callback = config;
  524. config = name;
  525. }
  526.  
  527. if (anim && !anim._running) {
  528. anim.init(this, config);
  529. } else {
  530. anim = new Transition(this._node, config);
  531. }
  532.  
  533. anim.run(callback);
  534. return this;
  535. };
  536.  
  537. Y.Node.prototype.show = function(name, config, callback) {
  538. this._show(); // show prior to transition
  539. if (name && Y.Transition) {
  540. if (typeof name !== 'string' && !name.push) { // named effect or array of effects supercedes default
  541. if (typeof config === 'function') {
  542. callback = config;
  543. config = name;
  544. }
  545. name = Transition.SHOW_TRANSITION;
  546. }
  547. this.transition(name, config, callback);
  548. }
  549. else if (name && !Y.Transition) { Y.log('unable to transition show; missing transition module', 'warn', 'node'); }
  550. return this;
  551. };
  552.  
  553. Y.NodeList.prototype.show = function(name, config, callback) {
  554. var nodes = this._nodes,
  555. i = 0,
  556. node;
  557.  
  558. while ((node = nodes[i++])) {
  559. Y.one(node).show(name, config, callback);
  560. }
  561.  
  562. return this;
  563. };
  564.  
  565.  
  566.  
  567. var _wrapCallBack = function(anim, fn, callback) {
  568. return function() {
  569. if (fn) {
  570. fn.call(anim);
  571. }
  572. if (callback && typeof callback === 'function') {
  573. callback.apply(anim._node, arguments);
  574. }
  575. };
  576. };
  577.  
  578. Y.Node.prototype.hide = function(name, config, callback) {
  579. if (name && Y.Transition) {
  580. if (typeof config === 'function') {
  581. callback = config;
  582. config = null;
  583. }
  584.  
  585. callback = _wrapCallBack(this, this._hide, callback); // wrap with existing callback
  586. if (typeof name !== 'string' && !name.push) { // named effect or array of effects supercedes default
  587. if (typeof config === 'function') {
  588. callback = config;
  589. config = name;
  590. }
  591. name = Transition.HIDE_TRANSITION;
  592. }
  593. this.transition(name, config, callback);
  594. } else if (name && !Y.Transition) { Y.log('unable to transition hide; missing transition module', 'warn', 'node');
  595. } else {
  596. this._hide();
  597. }
  598. return this;
  599. };
  600.  
  601. Y.NodeList.prototype.hide = function(name, config, callback) {
  602. var nodes = this._nodes,
  603. i = 0,
  604. node;
  605.  
  606. while ((node = nodes[i++])) {
  607. Y.one(node).hide(name, config, callback);
  608. }
  609.  
  610. return this;
  611. };
  612.  
  613. /**
  614. * Animate one or more css properties to a given value. Requires the "transition" module.
  615. * <pre>example usage:
  616. * Y.all('.demo').transition({
  617. * duration: 1, // in seconds, default is 0.5
  618. * easing: 'ease-out', // default is 'ease'
  619. * delay: '1', // delay start for 1 second, default is 0
  620. *
  621. * height: '10px',
  622. * width: '10px',
  623. *
  624. * opacity: { // per property
  625. * value: 0,
  626. * duration: 2,
  627. * delay: 2,
  628. * easing: 'ease-in'
  629. * }
  630. * });
  631. * </pre>
  632. * @for NodeList
  633. * @method transition
  634. * @param {Object} config An object containing one or more style properties, a duration and an easing.
  635. * @param {Function} callback A function to run after the transition has completed. The callback fires
  636. * once per item in the NodeList.
  637. * @chainable
  638. */
  639. Y.NodeList.prototype.transition = function(config, callback) {
  640. var nodes = this._nodes,
  641. i = 0,
  642. node;
  643.  
  644. while ((node = nodes[i++])) {
  645. Y.one(node).transition(config, callback);
  646. }
  647.  
  648. return this;
  649. };
  650.  
  651. Y.Node.prototype.toggleView = function(name, on, callback) {
  652. this._toggles = this._toggles || [];
  653. callback = arguments[arguments.length - 1];
  654.  
  655. if (typeof name !== 'string') { // no transition, just toggle
  656. on = name;
  657. this._toggleView(on, callback); // call original _toggleView in Y.Node
  658. return;
  659. }
  660.  
  661. if (typeof on === 'function') { // Ignore "on" if used for callback argument.
  662. on = undefined;
  663. }
  664.  
  665. if (typeof on === 'undefined' && name in this._toggles) { // reverse current toggle
  666. on = ! this._toggles[name];
  667. }
  668.  
  669. on = (on) ? 1 : 0;
  670. if (on) {
  671. this._show();
  672. } else {
  673. callback = _wrapCallBack(this, this._hide, callback);
  674. }
  675.  
  676. this._toggles[name] = on;
  677. this.transition(Y.Transition.toggles[name][on], callback);
  678.  
  679. return this;
  680. };
  681.  
  682. Y.NodeList.prototype.toggleView = function(name, on, callback) {
  683. var nodes = this._nodes,
  684. i = 0,
  685. node;
  686.  
  687. while ((node = nodes[i++])) {
  688. node = Y.one(node);
  689. node.toggleView.apply(node, arguments);
  690. }
  691.  
  692. return this;
  693. };
  694.  
  695. Y.mix(Transition.fx, {
  696. fadeOut: {
  697. opacity: 0,
  698. duration: 0.5,
  699. easing: 'ease-out'
  700. },
  701.  
  702. fadeIn: {
  703. opacity: 1,
  704. duration: 0.5,
  705. easing: 'ease-in'
  706. },
  707.  
  708. sizeOut: {
  709. height: 0,
  710. width: 0,
  711. duration: 0.75,
  712. easing: 'ease-out'
  713. },
  714.  
  715. sizeIn: {
  716. height: function(node) {
  717. return node.get('scrollHeight') + 'px';
  718. },
  719. width: function(node) {
  720. return node.get('scrollWidth') + 'px';
  721. },
  722. duration: 0.5,
  723. easing: 'ease-in',
  724.  
  725. on: {
  726. start: function() {
  727. var overflow = this.getStyle('overflow');
  728. if (overflow !== 'hidden') { // enable scrollHeight/Width
  729. this.setStyle('overflow', 'hidden');
  730. this._transitionOverflow = overflow;
  731. }
  732. },
  733.  
  734. end: function() {
  735. if (this._transitionOverflow) { // revert overridden value
  736. this.setStyle('overflow', this._transitionOverflow);
  737. delete this._transitionOverflow;
  738. }
  739. }
  740. }
  741. }
  742. });
  743.  
  744. Y.mix(Transition.toggles, {
  745. size: ['sizeOut', 'sizeIn'],
  746. fade: ['fadeOut', 'fadeIn']
  747. });
  748.