fx.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. define("dojo/_base/fx", ["./kernel", "./lang", "../Evented", "./Color", "./connect", "./sniff", "../dom", "../dom-style"], function(dojo, lang, Evented, Color, connect, has, dom, style){
  2. // module:
  3. // dojo/_base/fx
  4. // summary:
  5. // This module defines the base dojo.fx implementation.
  6. // notes:
  7. // Animation loosely package based on Dan Pupius' work, contributed under CLA; see
  8. // http://pupius.co.uk/js/Toolkit.Drawing.js
  9. var _mixin = lang.mixin;
  10. dojo._Line = function(/*int*/ start, /*int*/ end){
  11. // summary:
  12. // dojo._Line is the object used to generate values from a start value
  13. // to an end value
  14. // start: int
  15. // Beginning value for range
  16. // end: int
  17. // Ending value for range
  18. this.start = start;
  19. this.end = end;
  20. };
  21. dojo._Line.prototype.getValue = function(/*float*/ n){
  22. // summary: Returns the point on the line
  23. // n: a floating point number greater than 0 and less than 1
  24. return ((this.end - this.start) * n) + this.start; // Decimal
  25. };
  26. dojo.Animation = function(args){
  27. // summary:
  28. // A generic animation class that fires callbacks into its handlers
  29. // object at various states.
  30. // description:
  31. // A generic animation class that fires callbacks into its handlers
  32. // object at various states. Nearly all dojo animation functions
  33. // return an instance of this method, usually without calling the
  34. // .play() method beforehand. Therefore, you will likely need to
  35. // call .play() on instances of `dojo.Animation` when one is
  36. // returned.
  37. // args: Object
  38. // The 'magic argument', mixing all the properties into this
  39. // animation instance.
  40. _mixin(this, args);
  41. if(lang.isArray(this.curve)){
  42. this.curve = new dojo._Line(this.curve[0], this.curve[1]);
  43. }
  44. };
  45. dojo.Animation.prototype = new Evented();
  46. // Alias to drop come 2.0:
  47. dojo._Animation = dojo.Animation;
  48. lang.extend(dojo.Animation, {
  49. // duration: Integer
  50. // The time in milliseonds the animation will take to run
  51. duration: 350,
  52. /*=====
  53. // curve: dojo._Line|Array
  54. // A two element array of start and end values, or a `dojo._Line` instance to be
  55. // used in the Animation.
  56. curve: null,
  57. // easing: Function?
  58. // A Function to adjust the acceleration (or deceleration) of the progress
  59. // across a dojo._Line
  60. easing: null,
  61. =====*/
  62. // repeat: Integer?
  63. // The number of times to loop the animation
  64. repeat: 0,
  65. // rate: Integer?
  66. // the time in milliseconds to wait before advancing to next frame
  67. // (used as a fps timer: 1000/rate = fps)
  68. rate: 20 /* 50 fps */,
  69. /*=====
  70. // delay: Integer?
  71. // The time in milliseconds to wait before starting animation after it
  72. // has been .play()'ed
  73. delay: null,
  74. // beforeBegin: Event?
  75. // Synthetic event fired before a dojo.Animation begins playing (synchronous)
  76. beforeBegin: null,
  77. // onBegin: Event?
  78. // Synthetic event fired as a dojo.Animation begins playing (useful?)
  79. onBegin: null,
  80. // onAnimate: Event?
  81. // Synthetic event fired at each interval of a `dojo.Animation`
  82. onAnimate: null,
  83. // onEnd: Event?
  84. // Synthetic event fired after the final frame of a `dojo.Animation`
  85. onEnd: null,
  86. // onPlay: Event?
  87. // Synthetic event fired any time a `dojo.Animation` is play()'ed
  88. onPlay: null,
  89. // onPause: Event?
  90. // Synthetic event fired when a `dojo.Animation` is paused
  91. onPause: null,
  92. // onStop: Event
  93. // Synthetic event fires when a `dojo.Animation` is stopped
  94. onStop: null,
  95. =====*/
  96. _percent: 0,
  97. _startRepeatCount: 0,
  98. _getStep: function(){
  99. var _p = this._percent,
  100. _e = this.easing
  101. ;
  102. return _e ? _e(_p) : _p;
  103. },
  104. _fire: function(/*Event*/ evt, /*Array?*/ args){
  105. // summary:
  106. // Convenience function. Fire event "evt" and pass it the
  107. // arguments specified in "args".
  108. // description:
  109. // Convenience function. Fire event "evt" and pass it the
  110. // arguments specified in "args".
  111. // Fires the callback in the scope of the `dojo.Animation`
  112. // instance.
  113. // evt:
  114. // The event to fire.
  115. // args:
  116. // The arguments to pass to the event.
  117. var a = args||[];
  118. if(this[evt]){
  119. if(dojo.config.debugAtAllCosts){
  120. this[evt].apply(this, a);
  121. }else{
  122. try{
  123. this[evt].apply(this, a);
  124. }catch(e){
  125. // squelch and log because we shouldn't allow exceptions in
  126. // synthetic event handlers to cause the internal timer to run
  127. // amuck, potentially pegging the CPU. I'm not a fan of this
  128. // squelch, but hopefully logging will make it clear what's
  129. // going on
  130. console.error("exception in animation handler for:", evt);
  131. console.error(e);
  132. }
  133. }
  134. }
  135. return this; // dojo.Animation
  136. },
  137. play: function(/*int?*/ delay, /*Boolean?*/ gotoStart){
  138. // summary:
  139. // Start the animation.
  140. // delay:
  141. // How many milliseconds to delay before starting.
  142. // gotoStart:
  143. // If true, starts the animation from the beginning; otherwise,
  144. // starts it from its current position.
  145. // returns: dojo.Animation
  146. // The instance to allow chaining.
  147. var _t = this;
  148. if(_t._delayTimer){ _t._clearTimer(); }
  149. if(gotoStart){
  150. _t._stopTimer();
  151. _t._active = _t._paused = false;
  152. _t._percent = 0;
  153. }else if(_t._active && !_t._paused){
  154. return _t;
  155. }
  156. _t._fire("beforeBegin", [_t.node]);
  157. var de = delay || _t.delay,
  158. _p = lang.hitch(_t, "_play", gotoStart);
  159. if(de > 0){
  160. _t._delayTimer = setTimeout(_p, de);
  161. return _t;
  162. }
  163. _p();
  164. return _t; // dojo.Animation
  165. },
  166. _play: function(gotoStart){
  167. var _t = this;
  168. if(_t._delayTimer){ _t._clearTimer(); }
  169. _t._startTime = new Date().valueOf();
  170. if(_t._paused){
  171. _t._startTime -= _t.duration * _t._percent;
  172. }
  173. _t._active = true;
  174. _t._paused = false;
  175. var value = _t.curve.getValue(_t._getStep());
  176. if(!_t._percent){
  177. if(!_t._startRepeatCount){
  178. _t._startRepeatCount = _t.repeat;
  179. }
  180. _t._fire("onBegin", [value]);
  181. }
  182. _t._fire("onPlay", [value]);
  183. _t._cycle();
  184. return _t; // dojo.Animation
  185. },
  186. pause: function(){
  187. // summary: Pauses a running animation.
  188. var _t = this;
  189. if(_t._delayTimer){ _t._clearTimer(); }
  190. _t._stopTimer();
  191. if(!_t._active){ return _t; /*dojo.Animation*/ }
  192. _t._paused = true;
  193. _t._fire("onPause", [_t.curve.getValue(_t._getStep())]);
  194. return _t; // dojo.Animation
  195. },
  196. gotoPercent: function(/*Decimal*/ percent, /*Boolean?*/ andPlay){
  197. // summary:
  198. // Sets the progress of the animation.
  199. // percent:
  200. // A percentage in decimal notation (between and including 0.0 and 1.0).
  201. // andPlay:
  202. // If true, play the animation after setting the progress.
  203. var _t = this;
  204. _t._stopTimer();
  205. _t._active = _t._paused = true;
  206. _t._percent = percent;
  207. if(andPlay){ _t.play(); }
  208. return _t; // dojo.Animation
  209. },
  210. stop: function(/*boolean?*/ gotoEnd){
  211. // summary: Stops a running animation.
  212. // gotoEnd: If true, the animation will end.
  213. var _t = this;
  214. if(_t._delayTimer){ _t._clearTimer(); }
  215. if(!_t._timer){ return _t; /* dojo.Animation */ }
  216. _t._stopTimer();
  217. if(gotoEnd){
  218. _t._percent = 1;
  219. }
  220. _t._fire("onStop", [_t.curve.getValue(_t._getStep())]);
  221. _t._active = _t._paused = false;
  222. return _t; // dojo.Animation
  223. },
  224. status: function(){
  225. // summary:
  226. // Returns a string token representation of the status of
  227. // the animation, one of: "paused", "playing", "stopped"
  228. if(this._active){
  229. return this._paused ? "paused" : "playing"; // String
  230. }
  231. return "stopped"; // String
  232. },
  233. _cycle: function(){
  234. var _t = this;
  235. if(_t._active){
  236. var curr = new Date().valueOf();
  237. var step = (curr - _t._startTime) / (_t.duration);
  238. if(step >= 1){
  239. step = 1;
  240. }
  241. _t._percent = step;
  242. // Perform easing
  243. if(_t.easing){
  244. step = _t.easing(step);
  245. }
  246. _t._fire("onAnimate", [_t.curve.getValue(step)]);
  247. if(_t._percent < 1){
  248. _t._startTimer();
  249. }else{
  250. _t._active = false;
  251. if(_t.repeat > 0){
  252. _t.repeat--;
  253. _t.play(null, true);
  254. }else if(_t.repeat == -1){
  255. _t.play(null, true);
  256. }else{
  257. if(_t._startRepeatCount){
  258. _t.repeat = _t._startRepeatCount;
  259. _t._startRepeatCount = 0;
  260. }
  261. }
  262. _t._percent = 0;
  263. _t._fire("onEnd", [_t.node]);
  264. !_t.repeat && _t._stopTimer();
  265. }
  266. }
  267. return _t; // dojo.Animation
  268. },
  269. _clearTimer: function(){
  270. // summary: Clear the play delay timer
  271. clearTimeout(this._delayTimer);
  272. delete this._delayTimer;
  273. }
  274. });
  275. // the local timer, stubbed into all Animation instances
  276. var ctr = 0,
  277. timer = null,
  278. runner = {
  279. run: function(){}
  280. };
  281. lang.extend(dojo.Animation, {
  282. _startTimer: function(){
  283. if(!this._timer){
  284. this._timer = connect.connect(runner, "run", this, "_cycle");
  285. ctr++;
  286. }
  287. if(!timer){
  288. timer = setInterval(lang.hitch(runner, "run"), this.rate);
  289. }
  290. },
  291. _stopTimer: function(){
  292. if(this._timer){
  293. connect.disconnect(this._timer);
  294. this._timer = null;
  295. ctr--;
  296. }
  297. if(ctr <= 0){
  298. clearInterval(timer);
  299. timer = null;
  300. ctr = 0;
  301. }
  302. }
  303. });
  304. var _makeFadeable =
  305. has("ie") ? function(node){
  306. // only set the zoom if the "tickle" value would be the same as the
  307. // default
  308. var ns = node.style;
  309. // don't set the width to auto if it didn't already cascade that way.
  310. // We don't want to f anyones designs
  311. if(!ns.width.length && style.get(node, "width") == "auto"){
  312. ns.width = "auto";
  313. }
  314. } :
  315. function(){};
  316. dojo._fade = function(/*Object*/ args){
  317. // summary:
  318. // Returns an animation that will fade the node defined by
  319. // args.node from the start to end values passed (args.start
  320. // args.end) (end is mandatory, start is optional)
  321. args.node = dom.byId(args.node);
  322. var fArgs = _mixin({ properties: {} }, args),
  323. props = (fArgs.properties.opacity = {});
  324. props.start = !("start" in fArgs) ?
  325. function(){
  326. return +style.get(fArgs.node, "opacity")||0;
  327. } : fArgs.start;
  328. props.end = fArgs.end;
  329. var anim = dojo.animateProperty(fArgs);
  330. connect.connect(anim, "beforeBegin", lang.partial(_makeFadeable, fArgs.node));
  331. return anim; // dojo.Animation
  332. };
  333. /*=====
  334. dojo.__FadeArgs = function(node, duration, easing){
  335. // node: DOMNode|String
  336. // The node referenced in the animation
  337. // duration: Integer?
  338. // Duration of the animation in milliseconds.
  339. // easing: Function?
  340. // An easing function.
  341. this.node = node;
  342. this.duration = duration;
  343. this.easing = easing;
  344. }
  345. =====*/
  346. dojo.fadeIn = function(/*dojo.__FadeArgs*/ args){
  347. // summary:
  348. // Returns an animation that will fade node defined in 'args' from
  349. // its current opacity to fully opaque.
  350. return dojo._fade(_mixin({ end: 1 }, args)); // dojo.Animation
  351. };
  352. dojo.fadeOut = function(/*dojo.__FadeArgs*/ args){
  353. // summary:
  354. // Returns an animation that will fade node defined in 'args'
  355. // from its current opacity to fully transparent.
  356. return dojo._fade(_mixin({ end: 0 }, args)); // dojo.Animation
  357. };
  358. dojo._defaultEasing = function(/*Decimal?*/ n){
  359. // summary: The default easing function for dojo.Animation(s)
  360. return 0.5 + ((Math.sin((n + 1.5) * Math.PI)) / 2); // Decimal
  361. };
  362. var PropLine = function(properties){
  363. // PropLine is an internal class which is used to model the values of
  364. // an a group of CSS properties across an animation lifecycle. In
  365. // particular, the "getValue" function handles getting interpolated
  366. // values between start and end for a particular CSS value.
  367. this._properties = properties;
  368. for(var p in properties){
  369. var prop = properties[p];
  370. if(prop.start instanceof Color){
  371. // create a reusable temp color object to keep intermediate results
  372. prop.tempColor = new Color();
  373. }
  374. }
  375. };
  376. PropLine.prototype.getValue = function(r){
  377. var ret = {};
  378. for(var p in this._properties){
  379. var prop = this._properties[p],
  380. start = prop.start;
  381. if(start instanceof Color){
  382. ret[p] = Color.blendColors(start, prop.end, r, prop.tempColor).toCss();
  383. }else if(!lang.isArray(start)){
  384. ret[p] = ((prop.end - start) * r) + start + (p != "opacity" ? prop.units || "px" : 0);
  385. }
  386. }
  387. return ret;
  388. };
  389. /*=====
  390. dojo.declare("dojo.__AnimArgs", [dojo.__FadeArgs], {
  391. // Properties: Object?
  392. // A hash map of style properties to Objects describing the transition,
  393. // such as the properties of dojo._Line with an additional 'units' property
  394. properties: {}
  395. //TODOC: add event callbacks
  396. });
  397. =====*/
  398. dojo.animateProperty = function(/*dojo.__AnimArgs*/ args){
  399. // summary:
  400. // Returns an animation that will transition the properties of
  401. // node defined in `args` depending how they are defined in
  402. // `args.properties`
  403. //
  404. // description:
  405. // `dojo.animateProperty` is the foundation of most `dojo.fx`
  406. // animations. It takes an object of "properties" corresponding to
  407. // style properties, and animates them in parallel over a set
  408. // duration.
  409. //
  410. // example:
  411. // A simple animation that changes the width of the specified node.
  412. // | dojo.animateProperty({
  413. // | node: "nodeId",
  414. // | properties: { width: 400 },
  415. // | }).play();
  416. // Dojo figures out the start value for the width and converts the
  417. // integer specified for the width to the more expressive but
  418. // verbose form `{ width: { end: '400', units: 'px' } }` which you
  419. // can also specify directly. Defaults to 'px' if ommitted.
  420. //
  421. // example:
  422. // Animate width, height, and padding over 2 seconds... the
  423. // pedantic way:
  424. // | dojo.animateProperty({ node: node, duration:2000,
  425. // | properties: {
  426. // | width: { start: '200', end: '400', units:"px" },
  427. // | height: { start:'200', end: '400', units:"px" },
  428. // | paddingTop: { start:'5', end:'50', units:"px" }
  429. // | }
  430. // | }).play();
  431. // Note 'paddingTop' is used over 'padding-top'. Multi-name CSS properties
  432. // are written using "mixed case", as the hyphen is illegal as an object key.
  433. //
  434. // example:
  435. // Plug in a different easing function and register a callback for
  436. // when the animation ends. Easing functions accept values between
  437. // zero and one and return a value on that basis. In this case, an
  438. // exponential-in curve.
  439. // | dojo.animateProperty({
  440. // | node: "nodeId",
  441. // | // dojo figures out the start value
  442. // | properties: { width: { end: 400 } },
  443. // | easing: function(n){
  444. // | return (n==0) ? 0 : Math.pow(2, 10 * (n - 1));
  445. // | },
  446. // | onEnd: function(node){
  447. // | // called when the animation finishes. The animation
  448. // | // target is passed to this function
  449. // | }
  450. // | }).play(500); // delay playing half a second
  451. //
  452. // example:
  453. // Like all `dojo.Animation`s, animateProperty returns a handle to the
  454. // Animation instance, which fires the events common to Dojo FX. Use `dojo.connect`
  455. // to access these events outside of the Animation definiton:
  456. // | var anim = dojo.animateProperty({
  457. // | node:"someId",
  458. // | properties:{
  459. // | width:400, height:500
  460. // | }
  461. // | });
  462. // | dojo.connect(anim,"onEnd", function(){
  463. // | console.log("animation ended");
  464. // | });
  465. // | // play the animation now:
  466. // | anim.play();
  467. //
  468. // example:
  469. // Each property can be a function whose return value is substituted along.
  470. // Additionally, each measurement (eg: start, end) can be a function. The node
  471. // reference is passed direcly to callbacks.
  472. // | dojo.animateProperty({
  473. // | node:"mine",
  474. // | properties:{
  475. // | height:function(node){
  476. // | // shrink this node by 50%
  477. // | return dojo.position(node).h / 2
  478. // | },
  479. // | width:{
  480. // | start:function(node){ return 100; },
  481. // | end:function(node){ return 200; }
  482. // | }
  483. // | }
  484. // | }).play();
  485. //
  486. var n = args.node = dom.byId(args.node);
  487. if(!args.easing){ args.easing = dojo._defaultEasing; }
  488. var anim = new dojo.Animation(args);
  489. connect.connect(anim, "beforeBegin", anim, function(){
  490. var pm = {};
  491. for(var p in this.properties){
  492. // Make shallow copy of properties into pm because we overwrite
  493. // some values below. In particular if start/end are functions
  494. // we don't want to overwrite them or the functions won't be
  495. // called if the animation is reused.
  496. if(p == "width" || p == "height"){
  497. this.node.display = "block";
  498. }
  499. var prop = this.properties[p];
  500. if(lang.isFunction(prop)){
  501. prop = prop(n);
  502. }
  503. prop = pm[p] = _mixin({}, (lang.isObject(prop) ? prop: { end: prop }));
  504. if(lang.isFunction(prop.start)){
  505. prop.start = prop.start(n);
  506. }
  507. if(lang.isFunction(prop.end)){
  508. prop.end = prop.end(n);
  509. }
  510. var isColor = (p.toLowerCase().indexOf("color") >= 0);
  511. function getStyle(node, p){
  512. // dojo.style(node, "height") can return "auto" or "" on IE; this is more reliable:
  513. var v = { height: node.offsetHeight, width: node.offsetWidth }[p];
  514. if(v !== undefined){ return v; }
  515. v = style.get(node, p);
  516. return (p == "opacity") ? +v : (isColor ? v : parseFloat(v));
  517. }
  518. if(!("end" in prop)){
  519. prop.end = getStyle(n, p);
  520. }else if(!("start" in prop)){
  521. prop.start = getStyle(n, p);
  522. }
  523. if(isColor){
  524. prop.start = new Color(prop.start);
  525. prop.end = new Color(prop.end);
  526. }else{
  527. prop.start = (p == "opacity") ? +prop.start : parseFloat(prop.start);
  528. }
  529. }
  530. this.curve = new PropLine(pm);
  531. });
  532. connect.connect(anim, "onAnimate", lang.hitch(style, "set", anim.node));
  533. return anim; // dojo.Animation
  534. };
  535. dojo.anim = function( /*DOMNode|String*/ node,
  536. /*Object*/ properties,
  537. /*Integer?*/ duration,
  538. /*Function?*/ easing,
  539. /*Function?*/ onEnd,
  540. /*Integer?*/ delay){
  541. // summary:
  542. // A simpler interface to `dojo.animateProperty()`, also returns
  543. // an instance of `dojo.Animation` but begins the animation
  544. // immediately, unlike nearly every other Dojo animation API.
  545. // description:
  546. // `dojo.anim` is a simpler (but somewhat less powerful) version
  547. // of `dojo.animateProperty`. It uses defaults for many basic properties
  548. // and allows for positional parameters to be used in place of the
  549. // packed "property bag" which is used for other Dojo animation
  550. // methods.
  551. //
  552. // The `dojo.Animation` object returned from `dojo.anim` will be
  553. // already playing when it is returned from this function, so
  554. // calling play() on it again is (usually) a no-op.
  555. // node:
  556. // a DOM node or the id of a node to animate CSS properties on
  557. // duration:
  558. // The number of milliseconds over which the animation
  559. // should run. Defaults to the global animation default duration
  560. // (350ms).
  561. // easing:
  562. // An easing function over which to calculate acceleration
  563. // and deceleration of the animation through its duration.
  564. // A default easing algorithm is provided, but you may
  565. // plug in any you wish. A large selection of easing algorithms
  566. // are available in `dojo.fx.easing`.
  567. // onEnd:
  568. // A function to be called when the animation finishes
  569. // running.
  570. // delay:
  571. // The number of milliseconds to delay beginning the
  572. // animation by. The default is 0.
  573. // example:
  574. // Fade out a node
  575. // | dojo.anim("id", { opacity: 0 });
  576. // example:
  577. // Fade out a node over a full second
  578. // | dojo.anim("id", { opacity: 0 }, 1000);
  579. return dojo.animateProperty({ // dojo.Animation
  580. node: node,
  581. duration: duration || dojo.Animation.prototype.duration,
  582. properties: properties,
  583. easing: easing,
  584. onEnd: onEnd
  585. }).play(delay || 0);
  586. };
  587. return {
  588. _Line: dojo._Line,
  589. Animation: dojo.Animation,
  590. _fade: dojo._fade,
  591. fadeIn: dojo.fadeIn,
  592. fadeOut: dojo.fadeOut,
  593. _defaultEasing: dojo._defaultEasing,
  594. animateProperty: dojo.animateProperty,
  595. anim: dojo.anim
  596. };
  597. });