fx.js 19 KB

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