animate.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. define(function(require, exports, module) {
  2. "use strict";
  3. var Util = require('./util');
  4. var Timer = require('./timer');
  5. var Easing = require('./easing');
  6. var Base = require('./base');
  7. //transform
  8. var vendorTransform = Util.prefixStyle("transform");
  9. //transition webkitTransition MozTransition OTransition msTtransition
  10. var vendorTransition = Util.prefixStyle("transition");
  11. var vendorTransitionDuration = Util.prefixStyle("transitionDuration");
  12. var vendorTransformOrigin = Util.prefixStyle("transformOrigin");
  13. var vendorTransitionEnd = Util.vendor ? Util.prefixStyle("transitionEnd") : "transitionend";
  14. var vendorTransformStr = Util.vendor ? ["-", Util.vendor, "-transform"].join("") : "transform";
  15. var translateTpl = 'translateX({translateX}px) translateY({translateY}px) translateZ(0)';
  16. //limit attrs
  17. var animAttrs = {
  18. 'transform': true,
  19. 'opacity': true,
  20. 'scrollTop': true,
  21. 'scrollLeft': true
  22. };
  23. function myParse(v) {
  24. return Math.round(parseFloat(v) * 1e5) / 1e5;
  25. }
  26. function defaultDecompose() {
  27. return {
  28. translateX: 0,
  29. translateY: 0,
  30. rotate: 0,
  31. skewX: 0,
  32. skewY: 0,
  33. scaleX: 1,
  34. scaleY: 1
  35. };
  36. }
  37. function toMatrixArray(matrix) {
  38. matrix = matrix.split(/,/);
  39. matrix = Array.prototype.map.call(matrix, function(v) {
  40. return myParse(v);
  41. });
  42. return matrix;
  43. }
  44. function decomposeMatrix(matrix) {
  45. matrix = toMatrixArray(matrix);
  46. var scaleX, scaleY, skew,
  47. A = matrix[0],
  48. B = matrix[1],
  49. C = matrix[2],
  50. D = matrix[3];
  51. // Make sure matrix is not singular
  52. if (A * D - B * C) {
  53. scaleX = Math.sqrt(A * A + B * B);
  54. skew = (A * C + B * D) / (A * D - C * B);
  55. scaleY = (A * D - B * C) / scaleX;
  56. // step (6)
  57. if (A * D < B * C) {
  58. skew = -skew;
  59. scaleX = -scaleX;
  60. }
  61. // matrix is singular and cannot be interpolated
  62. } else {
  63. // In this case the elem shouldn't be rendered, hence scale == 0
  64. scaleX = scaleY = skew = 0;
  65. }
  66. // The recomposition order is very important
  67. // see http://hg.mozilla.org/mozilla-central/file/7cb3e9795d04/layout/style/nsStyleAnimation.cpp#l971
  68. return {
  69. translateX: myParse(matrix[4]),
  70. translateY: myParse(matrix[5]),
  71. rotate: myParse(Math.atan2(B, A) * 180 / Math.PI),
  72. skewX: myParse(Math.atan(skew) * 180 / Math.PI),
  73. skewY: 0,
  74. scaleX: myParse(scaleX),
  75. scaleY: myParse(scaleY)
  76. };
  77. }
  78. function getTransformInfo(transform) {
  79. transform = transform.split(')');
  80. var trim = Util.trim,
  81. i = -1,
  82. l = transform.length - 1,
  83. split, prop, val,
  84. ret = defaultDecompose();
  85. // Loop through the transform properties, parse and multiply them
  86. while (++i < l) {
  87. split = transform[i].split('(');
  88. prop = trim(split[0]);
  89. val = split[1];
  90. switch (prop) {
  91. case 'translateX':
  92. case 'translateY':
  93. case 'scaleX':
  94. case 'scaleY':
  95. ret[prop] = myParse(val);
  96. break;
  97. case 'translate':
  98. case 'translate3d':
  99. val = val.split(',');
  100. ret.translateX = myParse(val[0]);
  101. ret.translateY = myParse(val[1] || 0);
  102. break;
  103. case 'scale':
  104. val = val.split(',');
  105. ret.scaleX = myParse(val[0]);
  106. ret.scaleY = myParse(val[1] || val[0]);
  107. break;
  108. case 'matrix':
  109. return decomposeMatrix(val);
  110. }
  111. }
  112. return ret;
  113. }
  114. /**
  115. * animate function
  116. * @constructor
  117. * @param {HTMLElement} el element to animate
  118. * @param {Object} config config for animate
  119. * @param {Object} config.css
  120. * @param {Number} config.duration
  121. * @param {String} config.easing
  122. * @extends {Base}
  123. */
  124. function Animate(el, cfg) {
  125. if (!el || !cfg || !cfg.css) return;
  126. var self = this;
  127. self.cfg = cfg;
  128. self.el = el;
  129. var duration = cfg.duration || 0,
  130. easing = cfg.easing || "ease",
  131. delay = cfg.delay || 0;
  132. //trigger run
  133. if (cfg.run) {
  134. //frame animate
  135. self.timer = self.timer || new Timer({
  136. duration: Math.round(duration),
  137. easing: easing,
  138. });
  139. self.timer.on("run", cfg.run);
  140. }
  141. self._bindEvt();
  142. return self;
  143. }
  144. function computeTransform(prevTransform, destTransform) {
  145. var transform = getTransformInfo(prevTransform);
  146. var dest = getTransformInfo(destTransform);
  147. var trans = {};
  148. for (var i in dest) {
  149. trans[i] = {
  150. prevVal: transform[i],
  151. newVal: dest[i]
  152. }
  153. }
  154. return trans;
  155. }
  156. //for scroll only
  157. function setStyle(el, styleName, prevVal, newVal, percent) {
  158. prevVal = isNaN(Number(prevVal)) ? 0 : Number(prevVal);
  159. var curVal = ((newVal - prevVal) * percent + prevVal);
  160. css(el, styleName, curVal);
  161. }
  162. function css(el, styleName, val) {
  163. switch (styleName) {
  164. case "scrollTop":
  165. case "scrollLeft":
  166. el[styleName] = val;
  167. break;
  168. case "transform":
  169. el.style[vendorTransform] = val;
  170. case "opacity":
  171. el.style[styleName] = val;
  172. break;
  173. }
  174. }
  175. Util.extend(Animate, Base, {
  176. /**
  177. * to start the animation
  178. * @memberof Animate
  179. * @return {Animate}
  180. */
  181. run: function() {
  182. var self = this;
  183. var cfg = self.cfg,
  184. el = self.el,
  185. duration = cfg.duration || 0,
  186. easing = cfg.easing || "ease",
  187. delay = cfg.delay || 0;
  188. self.__isTransitionEnd = false;
  189. clearTimeout(self.__itv)
  190. self.timer && self.timer.run();
  191. if (duration <= Timer.MIN_DURATION) {
  192. for (var i in cfg.css) {
  193. css(el, i, cfg.css[i]);
  194. }
  195. self.stop()
  196. self.__handlers.stop.call(self);
  197. return;
  198. }
  199. if(Util.isBadAndroid()){
  200. //use frame animate on bad android device
  201. cfg.useTransition = false;
  202. }
  203. if (cfg.useTransition) {
  204. //transition
  205. el.style[vendorTransition] = Util.substitute('all {duration}ms {easing} {delay}ms', {
  206. duration: Math.round(duration),
  207. easing: Easing.format(easing),
  208. delay: delay
  209. });
  210. for (var i in cfg.css) {
  211. //set css
  212. css(el, i, cfg.css[i]);
  213. }
  214. self.__itv = setTimeout(function() {
  215. if (!self.__isTransitionEnd) {
  216. self.__isTransitionEnd = true;
  217. self.trigger("transitionend");
  218. }
  219. }, Number(duration) + 60);
  220. } else {
  221. self.computeStyle = self.computeStyle || window.getComputedStyle(el);
  222. //transform
  223. if (cfg.css.transform && self.timer) {
  224. var transmap = self.transmap = computeTransform(self.computeStyle[vendorTransform], cfg.css.transform);
  225. self.timer.off("run", self.__handlers.transRun);
  226. self.timer.on("run", self.__handlers.transRun, self);
  227. self.timer.off("end",self.__handlers.transRun);
  228. self.timer.on("end", self.__handlers.transRun, self);
  229. }
  230. }
  231. return self;
  232. },
  233. _transitionEndHandler: function(e) {
  234. var self = this;
  235. self.stop();
  236. self.__handlers.stop.call(self);
  237. },
  238. __handlers: {
  239. transRun: function(e) {
  240. var self = this;
  241. var transmap = self.transmap;
  242. var el = self.el;
  243. var newTrans = {};
  244. for (var i in transmap) {
  245. newTrans[i] = (transmap[i].newVal - transmap[i].prevVal) * e.percent + transmap[i].prevVal
  246. }
  247. var ret = Util.substitute(translateTpl + ' ' +
  248. 'scale({scaleX},{scaleY})', newTrans);
  249. el.style[vendorTransform] = ret;
  250. },
  251. stop: function(e) {
  252. var self = this;
  253. var cfg = self.cfg;
  254. cfg.end && cfg.end({
  255. percent: 1
  256. });
  257. }
  258. },
  259. _bindEvt: function() {
  260. var self = this;
  261. var cfg = self.cfg;
  262. var el = self.el;
  263. self.el.addEventListener(vendorTransitionEnd, function(e) {
  264. self.__isTransitionEnd = true;
  265. if (e.target !== e.currentTarget) return;
  266. self.trigger("transitionend", e);
  267. })
  268. self.on("transitionend", self._transitionEndHandler, self);
  269. var cssRun = function(e) {
  270. self.computeStyle = self.computeStyle || window.getComputedStyle(el);
  271. for (var i in cfg.css) {
  272. if (!/transform/.test(i)) {
  273. setStyle(self.el, i, self.computeStyle[i], cfg.css[i], e.percent);
  274. }
  275. }
  276. };
  277. self.timer && self.timer.on("run", cssRun);
  278. self.timer && self.timer.on("stop", self.__handlers.stop, self);
  279. },
  280. /**
  281. * to stop the animation
  282. * @memberof Animate
  283. * @return {Animate}
  284. */
  285. stop: function() {
  286. var self = this;
  287. if (self.cfg.useTransition && self.cfg.duration > Timer.MIN_DURATION) {
  288. var computeStyle = window.getComputedStyle(this.el);
  289. for (var i in self.cfg.css) {
  290. if (animAttrs[i]) {
  291. var value = /transform/.test(i) ? computeStyle[vendorTransform] : computeStyle[i];
  292. css(self.el, i, Util.substitute(translateTpl + ' ' + 'scale({scaleX},{scaleY})', getTransformInfo(value)));
  293. }
  294. }
  295. self.el.style[vendorTransition] = "none";
  296. }
  297. self.timer && self.timer.stop() && self.timer.reset();
  298. self.computeStyle = null;
  299. return self;
  300. },
  301. /**
  302. * to reset the animation to a new state
  303. * @memberof Animate
  304. * @param {object} cfg cfg for new animation
  305. * @return {Animate}
  306. */
  307. reset: function(cfg) {
  308. var self = this;
  309. self.computeStyle = null;
  310. Util.mix(self.cfg, cfg);
  311. this.timer && self.timer.reset({
  312. duration: Math.round(self.cfg.duration),
  313. easing: self.cfg.easing
  314. });
  315. return self;
  316. }
  317. });
  318. if (typeof module == 'object' && module.exports) {
  319. module.exports = Animate;
  320. }
  321. /** ignored by jsdoc **/
  322. else {
  323. return Animate;
  324. }
  325. });