fx.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. define([
  2. "./_base/lang",
  3. "./Evented",
  4. "./_base/kernel",
  5. "./_base/array",
  6. "./aspect",
  7. "./_base/fx",
  8. "./dom",
  9. "./dom-style",
  10. "./dom-geometry",
  11. "./ready",
  12. "require" // for context sensitive loading of Toggler
  13. ], function(lang, Evented, dojo, arrayUtil, aspect, baseFx, dom, domStyle, geom, ready, require){
  14. // module:
  15. // dojo/fx
  16. // For back-compat, remove in 2.0.
  17. if(!dojo.isAsync){
  18. ready(0, function(){
  19. var requires = ["./fx/Toggler"];
  20. require(requires); // use indirection so modules not rolled into a build
  21. });
  22. }
  23. var coreFx = dojo.fx = {
  24. // summary:
  25. // Effects library on top of Base animations
  26. };
  27. var _baseObj = {
  28. _fire: function(evt, args){
  29. if(this[evt]){
  30. this[evt].apply(this, args||[]);
  31. }
  32. return this;
  33. }
  34. };
  35. var _chain = function(animations){
  36. this._index = -1;
  37. this._animations = animations||[];
  38. this._current = this._onAnimateCtx = this._onEndCtx = null;
  39. this.duration = 0;
  40. arrayUtil.forEach(this._animations, function(a){
  41. this.duration += a.duration;
  42. if(a.delay){ this.duration += a.delay; }
  43. }, this);
  44. };
  45. _chain.prototype = new Evented();
  46. lang.extend(_chain, {
  47. _onAnimate: function(){
  48. this._fire("onAnimate", arguments);
  49. },
  50. _onEnd: function(){
  51. this._onAnimateCtx.remove();
  52. this._onEndCtx.remove();
  53. this._onAnimateCtx = this._onEndCtx = null;
  54. if(this._index + 1 == this._animations.length){
  55. this._fire("onEnd");
  56. }else{
  57. // switch animations
  58. this._current = this._animations[++this._index];
  59. this._onAnimateCtx = aspect.after(this._current, "onAnimate", lang.hitch(this, "_onAnimate"), true);
  60. this._onEndCtx = aspect.after(this._current, "onEnd", lang.hitch(this, "_onEnd"), true);
  61. this._current.play(0, true);
  62. }
  63. },
  64. play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
  65. if(!this._current){ this._current = this._animations[this._index = 0]; }
  66. if(!gotoStart && this._current.status() == "playing"){ return this; }
  67. var beforeBegin = aspect.after(this._current, "beforeBegin", lang.hitch(this, function(){
  68. this._fire("beforeBegin");
  69. }), true),
  70. onBegin = aspect.after(this._current, "onBegin", lang.hitch(this, function(arg){
  71. this._fire("onBegin", arguments);
  72. }), true),
  73. onPlay = aspect.after(this._current, "onPlay", lang.hitch(this, function(arg){
  74. this._fire("onPlay", arguments);
  75. beforeBegin.remove();
  76. onBegin.remove();
  77. onPlay.remove();
  78. }));
  79. if(this._onAnimateCtx){
  80. this._onAnimateCtx.remove();
  81. }
  82. this._onAnimateCtx = aspect.after(this._current, "onAnimate", lang.hitch(this, "_onAnimate"), true);
  83. if(this._onEndCtx){
  84. this._onEndCtx.remove();
  85. }
  86. this._onEndCtx = aspect.after(this._current, "onEnd", lang.hitch(this, "_onEnd"), true);
  87. this._current.play.apply(this._current, arguments);
  88. return this;
  89. },
  90. pause: function(){
  91. if(this._current){
  92. var e = aspect.after(this._current, "onPause", lang.hitch(this, function(arg){
  93. this._fire("onPause", arguments);
  94. e.remove();
  95. }), true);
  96. this._current.pause();
  97. }
  98. return this;
  99. },
  100. gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
  101. this.pause();
  102. var offset = this.duration * percent;
  103. this._current = null;
  104. arrayUtil.some(this._animations, function(a, index){
  105. if(offset <= a.duration){
  106. this._current = a;
  107. this._index = index;
  108. return true;
  109. }
  110. offset -= a.duration;
  111. return false;
  112. }, this);
  113. if(this._current){
  114. this._current.gotoPercent(offset / this._current.duration);
  115. }
  116. if (andPlay) { this.play(); }
  117. return this;
  118. },
  119. stop: function(/*boolean?*/ gotoEnd){
  120. if(this._current){
  121. if(gotoEnd){
  122. for(; this._index + 1 < this._animations.length; ++this._index){
  123. this._animations[this._index].stop(true);
  124. }
  125. this._current = this._animations[this._index];
  126. }
  127. var e = aspect.after(this._current, "onStop", lang.hitch(this, function(arg){
  128. this._fire("onStop", arguments);
  129. e.remove();
  130. }), true);
  131. this._current.stop();
  132. }
  133. return this;
  134. },
  135. status: function(){
  136. return this._current ? this._current.status() : "stopped";
  137. },
  138. destroy: function(){
  139. this.stop();
  140. if(this._onAnimateCtx){ this._onAnimateCtx.remove(); }
  141. if(this._onEndCtx){ this._onEndCtx.remove(); }
  142. }
  143. });
  144. lang.extend(_chain, _baseObj);
  145. coreFx.chain = function(/*dojo/_base/fx.Animation[]*/ animations){
  146. // summary:
  147. // Chain a list of `dojo/_base/fx.Animation`s to run in sequence
  148. //
  149. // description:
  150. // Return a `dojo/_base/fx.Animation` which will play all passed
  151. // `dojo/_base/fx.Animation` instances in sequence, firing its own
  152. // synthesized events simulating a single animation. (eg:
  153. // onEnd of this animation means the end of the chain,
  154. // not the individual animations within)
  155. //
  156. // example:
  157. // Once `node` is faded out, fade in `otherNode`
  158. // | require(["dojo/fx"], function(fx){
  159. // | fx.chain([
  160. // | fx.fadeIn({ node:node }),
  161. // | fx.fadeOut({ node:otherNode })
  162. // | ]).play();
  163. // | });
  164. //
  165. return new _chain(animations); // dojo/_base/fx.Animation
  166. };
  167. var _combine = function(animations){
  168. this._animations = animations||[];
  169. this._connects = [];
  170. this._finished = 0;
  171. this.duration = 0;
  172. arrayUtil.forEach(animations, function(a){
  173. var duration = a.duration;
  174. if(a.delay){ duration += a.delay; }
  175. if(this.duration < duration){ this.duration = duration; }
  176. this._connects.push(aspect.after(a, "onEnd", lang.hitch(this, "_onEnd"), true));
  177. }, this);
  178. this._pseudoAnimation = new baseFx.Animation({curve: [0, 1], duration: this.duration});
  179. var self = this;
  180. arrayUtil.forEach(["beforeBegin", "onBegin", "onPlay", "onAnimate", "onPause", "onStop", "onEnd"],
  181. function(evt){
  182. self._connects.push(aspect.after(self._pseudoAnimation, evt,
  183. function(){ self._fire(evt, arguments); },
  184. true));
  185. }
  186. );
  187. };
  188. lang.extend(_combine, {
  189. _doAction: function(action, args){
  190. arrayUtil.forEach(this._animations, function(a){
  191. a[action].apply(a, args);
  192. });
  193. return this;
  194. },
  195. _onEnd: function(){
  196. if(++this._finished > this._animations.length){
  197. this._fire("onEnd");
  198. }
  199. },
  200. _call: function(action, args){
  201. var t = this._pseudoAnimation;
  202. t[action].apply(t, args);
  203. },
  204. play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
  205. this._finished = 0;
  206. this._doAction("play", arguments);
  207. this._call("play", arguments);
  208. return this;
  209. },
  210. pause: function(){
  211. this._doAction("pause", arguments);
  212. this._call("pause", arguments);
  213. return this;
  214. },
  215. gotoPercent: function(/*Decimal*/percent, /*Boolean?*/ andPlay){
  216. var ms = this.duration * percent;
  217. arrayUtil.forEach(this._animations, function(a){
  218. a.gotoPercent(a.duration < ms ? 1 : (ms / a.duration), andPlay);
  219. });
  220. this._call("gotoPercent", arguments);
  221. return this;
  222. },
  223. stop: function(/*boolean?*/ gotoEnd){
  224. this._doAction("stop", arguments);
  225. this._call("stop", arguments);
  226. return this;
  227. },
  228. status: function(){
  229. return this._pseudoAnimation.status();
  230. },
  231. destroy: function(){
  232. this.stop();
  233. arrayUtil.forEach(this._connects, function(handle){
  234. handle.remove();
  235. });
  236. }
  237. });
  238. lang.extend(_combine, _baseObj);
  239. coreFx.combine = function(/*dojo/_base/fx.Animation[]*/ animations){
  240. // summary:
  241. // Combine a list of `dojo/_base/fx.Animation`s to run in parallel
  242. //
  243. // description:
  244. // Combine an array of `dojo/_base/fx.Animation`s to run in parallel,
  245. // providing a new `dojo/_base/fx.Animation` instance encompasing each
  246. // animation, firing standard animation events.
  247. //
  248. // example:
  249. // Fade out `node` while fading in `otherNode` simultaneously
  250. // | require(["dojo/fx"], function(fx){
  251. // | fx.combine([
  252. // | fx.fadeIn({ node:node }),
  253. // | fx.fadeOut({ node:otherNode })
  254. // | ]).play();
  255. // | });
  256. //
  257. // example:
  258. // When the longest animation ends, execute a function:
  259. // | require(["dojo/fx"], function(fx){
  260. // | var anim = fx.combine([
  261. // | fx.fadeIn({ node: n, duration:700 }),
  262. // | fx.fadeOut({ node: otherNode, duration: 300 })
  263. // | ]);
  264. // | aspect.after(anim, "onEnd", function(){
  265. // | // overall animation is done.
  266. // | }, true);
  267. // | anim.play(); // play the animation
  268. // | });
  269. //
  270. return new _combine(animations); // dojo/_base/fx.Animation
  271. };
  272. coreFx.wipeIn = function(/*Object*/ args){
  273. // summary:
  274. // Expand a node to it's natural height.
  275. //
  276. // description:
  277. // Returns an animation that will expand the
  278. // node defined in 'args' object from it's current height to
  279. // it's natural height (with no scrollbar).
  280. // Node must have no margin/border/padding.
  281. //
  282. // args: Object
  283. // A hash-map of standard `dojo/_base/fx.Animation` constructor properties
  284. // (such as easing: node: duration: and so on)
  285. //
  286. // example:
  287. // | require(["dojo/fx"], function(fx){
  288. // | fx.wipeIn({
  289. // | node:"someId"
  290. // | }).play()
  291. // | });
  292. var node = args.node = dom.byId(args.node), s = node.style, o;
  293. var anim = baseFx.animateProperty(lang.mixin({
  294. properties: {
  295. height: {
  296. // wrapped in functions so we wait till the last second to query (in case value has changed)
  297. start: function(){
  298. // start at current [computed] height, but use 1px rather than 0
  299. // because 0 causes IE to display the whole panel
  300. o = s.overflow;
  301. s.overflow = "hidden";
  302. if(s.visibility == "hidden" || s.display == "none"){
  303. s.height = "1px";
  304. s.display = "";
  305. s.visibility = "";
  306. return 1;
  307. }else{
  308. var height = domStyle.get(node, "height");
  309. return Math.max(height, 1);
  310. }
  311. },
  312. end: function(){
  313. return node.scrollHeight;
  314. }
  315. }
  316. }
  317. }, args));
  318. var fini = function(){
  319. s.height = "auto";
  320. s.overflow = o;
  321. };
  322. aspect.after(anim, "onStop", fini, true);
  323. aspect.after(anim, "onEnd", fini, true);
  324. return anim; // dojo/_base/fx.Animation
  325. };
  326. coreFx.wipeOut = function(/*Object*/ args){
  327. // summary:
  328. // Shrink a node to nothing and hide it.
  329. //
  330. // description:
  331. // Returns an animation that will shrink node defined in "args"
  332. // from it's current height to 1px, and then hide it.
  333. //
  334. // args: Object
  335. // A hash-map of standard `dojo/_base/fx.Animation` constructor properties
  336. // (such as easing: node: duration: and so on)
  337. //
  338. // example:
  339. // | require(["dojo/fx"], function(fx){
  340. // | fx.wipeOut({ node:"someId" }).play()
  341. // | });
  342. var node = args.node = dom.byId(args.node), s = node.style, o;
  343. var anim = baseFx.animateProperty(lang.mixin({
  344. properties: {
  345. height: {
  346. end: 1 // 0 causes IE to display the whole panel
  347. }
  348. }
  349. }, args));
  350. aspect.after(anim, "beforeBegin", function(){
  351. o = s.overflow;
  352. s.overflow = "hidden";
  353. s.display = "";
  354. }, true);
  355. var fini = function(){
  356. s.overflow = o;
  357. s.height = "auto";
  358. s.display = "none";
  359. };
  360. aspect.after(anim, "onStop", fini, true);
  361. aspect.after(anim, "onEnd", fini, true);
  362. return anim; // dojo/_base/fx.Animation
  363. };
  364. coreFx.slideTo = function(/*Object*/ args){
  365. // summary:
  366. // Slide a node to a new top/left position
  367. //
  368. // description:
  369. // Returns an animation that will slide "node"
  370. // defined in args Object from its current position to
  371. // the position defined by (args.left, args.top).
  372. //
  373. // args: Object
  374. // A hash-map of standard `dojo/_base/fx.Animation` constructor properties
  375. // (such as easing: node: duration: and so on). Special args members
  376. // are `top` and `left`, which indicate the new position to slide to.
  377. //
  378. // example:
  379. // | .slideTo({ node: node, left:"40", top:"50", units:"px" }).play()
  380. var node = args.node = dom.byId(args.node),
  381. top = null, left = null;
  382. var init = (function(n){
  383. return function(){
  384. var cs = domStyle.getComputedStyle(n);
  385. var pos = cs.position;
  386. top = (pos == 'absolute' ? n.offsetTop : parseInt(cs.top) || 0);
  387. left = (pos == 'absolute' ? n.offsetLeft : parseInt(cs.left) || 0);
  388. if(pos != 'absolute' && pos != 'relative'){
  389. var ret = geom.position(n, true);
  390. top = ret.y;
  391. left = ret.x;
  392. n.style.position="absolute";
  393. n.style.top=top+"px";
  394. n.style.left=left+"px";
  395. }
  396. };
  397. })(node);
  398. init();
  399. var anim = baseFx.animateProperty(lang.mixin({
  400. properties: {
  401. top: args.top || 0,
  402. left: args.left || 0
  403. }
  404. }, args));
  405. aspect.after(anim, "beforeBegin", init, true);
  406. return anim; // dojo/_base/fx.Animation
  407. };
  408. return coreFx;
  409. });