jquery.spritely-0.6.1.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /*
  2. * jQuery spritely 0.6.1
  3. * http://spritely.net/
  4. *
  5. * Documentation:
  6. * http://spritely.net/documentation/
  7. *
  8. * Copyright 2010-2011, Peter Chater, Artlogic Media Ltd, http://www.artlogic.net/
  9. * Dual licensed under the MIT or GPL Version 2 licenses.
  10. *
  11. * Change history:
  12. * Version 0.6.1
  13. * - added some refinements from Gary hussey (http://bossninja.com/). Thanks Gary.
  14. * spritely now correctly clears timeouts/intervals when destroying sprites.
  15. * - added a goToFrame() method so you can set the current frame at any point.
  16. * - fixed the .spStop method where the 'last FPS' value was not being set, and the user specified FPS being ignore when .spStart was called.
  17. * Version 0.6
  18. * - added events to the .sprite() method: on_first_frame, on_last_frame, on_frame:
  19. * $('#sprite').sprite({
  20. * fps: 9,
  21. * no_of_frames: 24,
  22. * on_first_frame: function(obj) {
  23. * obj.spState(1); // change to state 1 (first row) on frame 1
  24. * },
  25. * on_last_frame: function(obj) {
  26. * obj.spStop(); // stop the animation on the last frame
  27. * },
  28. * on_frame: {
  29. * 8: function(obj) {
  30. * obj.spState(2); // change to state 2 (row 2) on frame 8
  31. * },
  32. * 16: function(obj) {
  33. * obj.spState(3); // change to state 3 (row 3) on frame 16
  34. * }
  35. * }
  36. * });
  37. * - added start_at_frame: $('#sprite').sprite({fps: 9, no_of_frames: 24, start_at_frame: 8});
  38. * Version 0.5
  39. * - added a 'destroy()' method which will stop the element's sprite behaviour, without actually removing the element. Example: $('#my_sprite').destroy()
  40. * Version 0.4
  41. * - add up/down for 'pan' method. <ricky.hewitt@artlogic.net>
  42. * - added 'dir' option for Sprites. This means a Sprite can be played in reverse.
  43. * - removed alert message regarding jQuery.draggable (now uses console.log, if available) <ricky.hewitt@artlogic.net>
  44. * Version 0.3b
  45. * - added lockTo method for locking sprites to background images. $('#sprite').lockTo('#background, {'left': 380, 'top': -60, 'bg_img_width': 1110});
  46. * Version 0.2.1
  47. * - animate function will stop cycling after play_frames has completed
  48. * Version 0.2 beta
  49. * - added isDraggable method (requires jquery-ui) $('#sprite').sprite().isDraggable({start: null, stop: function() {alert('Ouch! You dropped me!')});
  50. * - sprites may be set to play a limited number of frames when instantiated, e.g. $('#sprite').sprite({fps: 9, no_of_frames: 3, play_frames: 30})
  51. * - sprite speed may be controlled at any point by setting the frames-per-second $('#sprite').fps(20);
  52. * - sprites with multiple rows of frames may have there 'state' changed, e.g. to make the second row of frames
  53. * active, use: $('#sprite').spState(2); - to return to the first row, use $('#sprite').spState(1);
  54. * - background element speed may be controlled at any point with .spSpeed(), e.g. $('#bg1').spSpeed(10)
  55. * - background elements may be set to a depth where 100 is the viewer (up close) and 0 is the horizon, e.g.:
  56. * $('#bg1').pan({fps: 30, speed: 2, dir: 'left', depth: 30});
  57. * $('#bg2').pan({fps: 30, speed: 3, dir: 'left', depth: 70});
  58. * relative speed of backgrounds may now be set in a single action with $('#bg1, #bg2').spRelSpeed(20);
  59. * which will make elements closer to the horizon (lower depths) move slower than closer elements (higher depths)
  60. */
  61. (function($) {
  62. $._spritely = {
  63. // shared methods and variables used by spritely plugin
  64. animate: function(options) {
  65. var el = $(options.el);
  66. var el_id = el.attr('id');
  67. if (!$._spritely.instances[el_id]) {
  68. return this;
  69. }
  70. options = $.extend(options, $._spritely.instances[el_id] || {});
  71. if (options.play_frames && !$._spritely.instances[el_id]['remaining_frames']) {
  72. $._spritely.instances[el_id]['remaining_frames'] = options.play_frames + 1;
  73. }
  74. if (options.type == 'sprite' && options.fps) {
  75. var frames;
  76. var animate = function(el) {
  77. var w = options.width, h = options.height;
  78. if (!frames) {
  79. frames = [];
  80. total = 0
  81. for (var i = 0; i < options.no_of_frames; i ++) {
  82. frames[frames.length] = (0 - total);
  83. total += w;
  84. }
  85. }
  86. if ($._spritely.instances[el_id]['current_frame'] == 0) {
  87. if (options.on_first_frame) {
  88. options.on_first_frame(el);
  89. }
  90. } else if ($._spritely.instances[el_id]['current_frame'] == frames.length - 1) {
  91. if (options.on_last_frame) {
  92. options.on_last_frame(el);
  93. }
  94. }
  95. if (options.on_frame && options.on_frame[$._spritely.instances[el_id]['current_frame']]) {
  96. options.on_frame[$._spritely.instances[el_id]['current_frame']](el);
  97. }
  98. if (options.rewind == true) {
  99. if ($._spritely.instances[el_id]['current_frame'] <= 0) {
  100. $._spritely.instances[el_id]['current_frame'] = frames.length - 1;
  101. } else {
  102. $._spritely.instances[el_id]['current_frame'] = $._spritely.instances[el_id]['current_frame'] - 1;
  103. };
  104. } else {
  105. if ($._spritely.instances[el_id]['current_frame'] >= frames.length - 1) {
  106. $._spritely.instances[el_id]['current_frame'] = 0;
  107. } else {
  108. $._spritely.instances[el_id]['current_frame'] = $._spritely.instances[el_id]['current_frame'] + 1;
  109. }
  110. }
  111. var yPos = $._spritely.getBgY(el);
  112. el.css('background-position', frames[$._spritely.instances[el_id]['current_frame']] + 'px ' + yPos);
  113. if (options.bounce && options.bounce[0] > 0 && options.bounce[1] > 0) {
  114. var ud = options.bounce[0]; // up-down
  115. var lr = options.bounce[1]; // left-right
  116. var ms = options.bounce[2]; // milliseconds
  117. el
  118. .animate({top: '+=' + ud + 'px', left: '-=' + lr + 'px'}, ms)
  119. .animate({top: '-=' + ud + 'px', left: '+=' + lr + 'px'}, ms);
  120. }
  121. }
  122. if ($._spritely.instances[el_id]['remaining_frames'] && $._spritely.instances[el_id]['remaining_frames'] > 0) {
  123. $._spritely.instances[el_id]['remaining_frames'] --;
  124. if ($._spritely.instances[el_id]['remaining_frames'] == 0) {
  125. $._spritely.instances[el_id]['remaining_frames'] = -1;
  126. delete $._spritely.instances[el_id]['remaining_frames'];
  127. return;
  128. } else {
  129. animate(el);
  130. }
  131. } else if ($._spritely.instances[el_id]['remaining_frames'] != -1) {
  132. animate(el);
  133. }
  134. } else if (options.type == 'pan') {
  135. if (!$._spritely.instances[el_id]['_stopped']) {
  136. if (options.dir == 'up') {
  137. $._spritely.instances[el_id]['l'] = $._spritely.getBgX(el).replace('px', '');
  138. $._spritely.instances[el_id]['t'] = ($._spritely.instances[el_id]['t'] - (options.speed || 1)) || 0;
  139. }
  140. else if (options.dir == 'down') {
  141. $._spritely.instances[el_id]['l'] = $._spritely.getBgX(el).replace('px', '');
  142. $._spritely.instances[el_id]['t'] = ($._spritely.instances[el_id]['t'] + (options.speed || 1)) || 0;
  143. }
  144. else if (options.dir == 'left') {
  145. $._spritely.instances[el_id]['l'] = ($._spritely.instances[el_id]['l'] - (options.speed || 1)) || 0;
  146. $._spritely.instances[el_id]['t'] = $._spritely.getBgY(el).replace('px', '');
  147. } else {
  148. $._spritely.instances[el_id]['l'] = ($._spritely.instances[el_id]['l'] + (options.speed || 1)) || 0;
  149. $._spritely.instances[el_id]['t'] = $._spritely.getBgY(el).replace('px', '');
  150. }
  151. // When assembling the background-position string, care must be taken
  152. // to ensure correct formatting.. <ricky.hewitt@artlogic.net>
  153. var bg_left = $._spritely.instances[el_id]['l'].toString();
  154. if (bg_left.indexOf('%') == -1) {
  155. bg_left += 'px ';
  156. } else { bg_left += ' '; }
  157. var bg_top = $._spritely.instances[el_id]['t'].toString();
  158. if (bg_top.indexOf('%') == -1) {
  159. bg_top += 'px ';
  160. } else { bg_top += ' '; }
  161. $(el).css('background-position', bg_left + bg_top);
  162. }
  163. }
  164. $._spritely.instances[el_id]['options'] = options;
  165. $._spritely.instances[el_id]['timeout'] = window.setTimeout(function() {
  166. $._spritely.animate(options);
  167. }, parseInt(1000 / options.fps));
  168. },
  169. randomIntBetween: function(lower, higher) {
  170. return parseInt(rand_no = Math.floor((higher - (lower - 1)) * Math.random()) + lower);
  171. },
  172. getBgY: function(el) {
  173. if ($.browser.msie) {
  174. // fixme - the background-position property does not work
  175. // correctly in IE so we have to hack it here... Not ideal
  176. // especially as $.browser is depricated
  177. var bgY = $(el).css('background-position-y') || '0';
  178. } else {
  179. var bgY = ($(el).css('background-position') || ' ').split(' ')[1];
  180. }
  181. return bgY;
  182. },
  183. getBgX: function(el) {
  184. if ($.browser.msie) {
  185. // see note, above
  186. var bgX = $(el).css('background-position-x') || '0';
  187. } else {
  188. var bgX = ($(el).css('background-position') || ' ').split(' ')[0];
  189. }
  190. return bgX;
  191. },
  192. get_rel_pos: function(pos, w) {
  193. // return the position of an item relative to a background
  194. // image of width given by w
  195. var r = pos;
  196. if (pos < 0) {
  197. while (r < 0) {
  198. r += w;
  199. }
  200. } else {
  201. while (r > w) {
  202. r -= w;
  203. }
  204. }
  205. return r;
  206. }
  207. };
  208. $.fn.extend({
  209. spritely: function(options) {
  210. var options = $.extend({
  211. type: 'sprite',
  212. do_once: false,
  213. width: null,
  214. height: null,
  215. fps: 12,
  216. no_of_frames: 2,
  217. stop_after: null
  218. }, options || {});
  219. var el_id = $(this).attr('id');
  220. if (!$._spritely.instances) {
  221. $._spritely.instances = {};
  222. }
  223. if (!$._spritely.instances[el_id]) {
  224. if (options.start_at_frame) {
  225. $._spritely.instances[el_id] = {current_frame: options.start_at_frame - 1};
  226. } else {
  227. $._spritely.instances[el_id] = {current_frame: -1};
  228. }
  229. }
  230. $._spritely.instances[el_id]['type'] = options.type;
  231. $._spritely.instances[el_id]['depth'] = options.depth;
  232. options.el = this;
  233. options.width = options.width || $(this).width() || 100;
  234. options.height = options.height || $(this).height() || 100;
  235. var get_rate = function() {
  236. return parseInt(1000 / options.fps);
  237. }
  238. if (!options.do_once) {
  239. window.setTimeout(function() {
  240. $._spritely.animate(options);
  241. }, get_rate(options.fps));
  242. } else {
  243. $._spritely.animate(options);
  244. }
  245. return this; // so we can chain events
  246. },
  247. sprite: function(options) {
  248. var options = $.extend({
  249. type: 'sprite',
  250. bounce: [0, 0, 1000] // up-down, left-right, milliseconds
  251. }, options || {});
  252. return $(this).spritely(options);
  253. },
  254. pan: function(options) {
  255. var options = $.extend({
  256. type: 'pan',
  257. dir: 'left',
  258. continuous: true,
  259. speed: 1 // 1 pixel per frame
  260. }, options || {});
  261. return $(this).spritely(options);
  262. },
  263. flyToTap: function(options) {
  264. var options = $.extend({
  265. el_to_move: null,
  266. type: 'moveToTap',
  267. ms: 1000, // milliseconds
  268. do_once: true
  269. }, options || {});
  270. if (options.el_to_move) {
  271. $(options.el_to_move).active();
  272. }
  273. if ($._spritely.activeSprite) {
  274. if (window.Touch) { // iphone method see http://cubiq.org/remove-onclick-delay-on-webkit-for-iphone/9 or http://www.nimblekit.com/tutorials.html for clues...
  275. $(this)[0].ontouchstart = function(e) {
  276. var el_to_move = $._spritely.activeSprite;
  277. var touch = e.touches[0];
  278. var t = touch.pageY - (el_to_move.height() / 2);
  279. var l = touch.pageX - (el_to_move.width() / 2);
  280. el_to_move.animate({
  281. top: t + 'px',
  282. left: l + 'px'
  283. }, 1000);
  284. };
  285. } else {
  286. $(this).click(function(e) {
  287. var el_to_move = $._spritely.activeSprite;
  288. $(el_to_move).stop(true);
  289. var w = el_to_move.width();
  290. var h = el_to_move.height();
  291. var l = e.pageX - (w / 2);
  292. var t = e.pageY - (h / 2);
  293. el_to_move.animate({
  294. top: t + 'px',
  295. left: l + 'px'
  296. }, 1000);
  297. });
  298. }
  299. }
  300. return this;
  301. },
  302. // isDraggable requires jQuery ui
  303. isDraggable: function(options) {
  304. if ((!$(this).draggable)) {
  305. //console.log('To use the isDraggable method you need to load jquery-ui.js');
  306. return this;
  307. }
  308. var options = $.extend({
  309. type: 'isDraggable',
  310. start: null,
  311. stop: null,
  312. drag: null
  313. }, options || {});
  314. var el_id = $(this).attr('id');
  315. if (!$._spritely.instances[el_id]) {
  316. return this;
  317. }
  318. $._spritely.instances[el_id].isDraggableOptions = options;
  319. $(this).draggable({
  320. start: function() {
  321. var el_id = $(this).attr('id');
  322. $._spritely.instances[el_id].stop_random = true;
  323. $(this).stop(true);
  324. if ($._spritely.instances[el_id].isDraggableOptions.start) {
  325. $._spritely.instances[el_id].isDraggableOptions.start(this);
  326. }
  327. },
  328. drag: options.drag,
  329. stop: function() {
  330. var el_id = $(this).attr('id');
  331. $._spritely.instances[el_id].stop_random = false;
  332. if ($._spritely.instances[el_id].isDraggableOptions.stop) {
  333. $._spritely.instances[el_id].isDraggableOptions.stop(this);
  334. }
  335. }
  336. });
  337. return this;
  338. },
  339. active: function() {
  340. // the active sprite
  341. $._spritely.activeSprite = this;
  342. return this;
  343. },
  344. activeOnClick: function() {
  345. // make this the active script if clicked...
  346. var el = $(this);
  347. if (window.Touch) { // iphone method see http://cubiq.org/remove-onclick-delay-on-webkit-for-iphone/9 or http://www.nimblekit.com/tutorials.html for clues...
  348. el[0].ontouchstart = function(e) {
  349. $._spritely.activeSprite = el;
  350. };
  351. } else {
  352. el.click(function(e) {
  353. $._spritely.activeSprite = el;
  354. });
  355. }
  356. return this;
  357. },
  358. spRandom: function(options) {
  359. var options = $.extend({
  360. top: 50,
  361. left: 50,
  362. right: 290,
  363. bottom: 320,
  364. speed: 4000,
  365. pause: 0
  366. }, options || {});
  367. var el_id = $(this).attr('id');
  368. if (!$._spritely.instances[el_id]) {
  369. return this;
  370. }
  371. if (!$._spritely.instances[el_id].stop_random) {
  372. var r = $._spritely.randomIntBetween;
  373. var t = r(options.top, options.bottom);
  374. var l = r(options.left, options.right);
  375. $('#' + el_id).animate({
  376. top: t + 'px',
  377. left: l + 'px'
  378. }, options.speed)
  379. }
  380. window.setTimeout(function() {
  381. $('#' + el_id).spRandom(options);
  382. }, options.speed + options.pause)
  383. return this;
  384. },
  385. makeAbsolute: function() {
  386. // remove an element from its current position in the DOM and
  387. // position it absolutely, appended to the body tag.
  388. return this.each(function() {
  389. var el = $(this);
  390. var pos = el.position();
  391. el.css({position: "absolute", marginLeft: 0, marginTop: 0, top: pos.top, left: pos.left })
  392. .remove()
  393. .appendTo("body");
  394. });
  395. },
  396. spSet: function(prop_name, prop_value) {
  397. var el_id = $(this).attr('id');
  398. $._spritely.instances[el_id][prop_name] = prop_value;
  399. return this;
  400. },
  401. spGet: function(prop_name, prop_value) {
  402. var el_id = $(this).attr('id');
  403. return $._spritely.instances[el_id][prop_name];
  404. },
  405. spStop: function(bool) {
  406. $(this).each(function() {
  407. var el_id = $(this).attr('id');
  408. if ($._spritely.instances[el_id]['options']['fps']) {
  409. $._spritely.instances[el_id]['_last_fps'] = $._spritely.instances[el_id]['options']['fps'];
  410. }
  411. $._spritely.instances[el_id]['_stopped'] = true;
  412. $._spritely.instances[el_id]['_stopped_f1'] = bool;
  413. if ($._spritely.instances[el_id]['type'] == 'sprite') {
  414. $(this).spSet('fps', 0);
  415. }
  416. if (bool) {
  417. // set background image position to 0
  418. var bp_top = $._spritely.getBgY($(this));
  419. $(this).css('background-position', '0 ' + bp_top);
  420. }
  421. });
  422. return this;
  423. },
  424. spStart: function() {
  425. $(this).each(function() {
  426. var el_id = $(this).attr('id');
  427. var fps = $._spritely.instances[el_id]['_last_fps'] || 12;
  428. if ($._spritely.instances[el_id]['type'] == 'sprite') {
  429. $(this).spSet('fps', fps);
  430. }
  431. $._spritely.instances[el_id]['_stopped'] = false;
  432. });
  433. return this;
  434. },
  435. spToggle: function() {
  436. var el_id = $(this).attr('id');
  437. var stopped = $._spritely.instances[el_id]['_stopped'] || false;
  438. var stopped_f1 = $._spritely.instances[el_id]['_stopped_f1'] || false;
  439. if (stopped) {
  440. $(this).spStart();
  441. } else {
  442. $(this).spStop(stopped_f1);
  443. }
  444. return this;
  445. },
  446. fps: function(fps) {
  447. $(this).each(function() {
  448. $(this).spSet('fps', fps);
  449. });
  450. return this;
  451. },
  452. goToFrame: function(n) {
  453. var el_id = $(this).attr('id');
  454. if ($._spritely.instances && $._spritely.instances[el_id]) {
  455. $._spritely.instances[el_id]['current_frame'] = n - 1;
  456. }
  457. return this;
  458. },
  459. spSpeed: function(speed) {
  460. $(this).each(function() {
  461. $(this).spSet('speed', speed);
  462. });
  463. return this;
  464. },
  465. spRelSpeed: function(speed) {
  466. $(this).each(function() {
  467. var rel_depth = $(this).spGet('depth') / 100;
  468. $(this).spSet('speed', speed * rel_depth);
  469. });
  470. return this;
  471. },
  472. spChangeDir: function(dir) {
  473. $(this).each(function() {
  474. $(this).spSet('dir', dir);
  475. });
  476. return this;
  477. },
  478. spState: function(n) {
  479. $(this).each(function() {
  480. // change state of a sprite, where state is the vertical
  481. // position of the background image (e.g. frames row)
  482. var yPos = ((n - 1) * $(this).height()) + 'px';
  483. var xPos = $._spritely.getBgX($(this));
  484. var bp = xPos + ' -' + yPos;
  485. $(this).css('background-position', bp);
  486. });
  487. return this;
  488. },
  489. lockTo: function(el, options) {
  490. $(this).each(function() {
  491. var el_id = $(this).attr('id');
  492. if (!$._spritely.instances[el_id]) {
  493. return this;
  494. }
  495. $._spritely.instances[el_id]['locked_el'] = $(this);
  496. $._spritely.instances[el_id]['lock_to'] = $(el);
  497. $._spritely.instances[el_id]['lock_to_options'] = options;
  498. $._spritely.instances[el_id]['interval'] = window.setInterval(function() {
  499. if ($._spritely.instances[el_id]['lock_to']) {
  500. var locked_el = $._spritely.instances[el_id]['locked_el'];
  501. var locked_to_el = $._spritely.instances[el_id]['lock_to'];
  502. var locked_to_options = $._spritely.instances[el_id]['lock_to_options'];
  503. var locked_to_el_w = locked_to_options.bg_img_width;
  504. var locked_to_el_h = locked_to_el.height();
  505. var locked_to_el_y = $._spritely.getBgY(locked_to_el);
  506. var locked_to_el_x = $._spritely.getBgX(locked_to_el);
  507. var el_l = (parseInt(locked_to_el_x) + parseInt(locked_to_options['left']));
  508. var el_t = (parseInt(locked_to_el_y) + parseInt(locked_to_options['top']));
  509. el_l = $._spritely.get_rel_pos(el_l, locked_to_el_w);
  510. $(locked_el).css({
  511. 'top': el_t + 'px',
  512. 'left': el_l + 'px'
  513. });
  514. }
  515. }, options.interval || 20);
  516. });
  517. return this;
  518. },
  519. destroy: function() {
  520. var el = $(this);
  521. var el_id = $(this).attr('id');
  522. if ($._spritely.instances[el_id] && $._spritely.instances[el_id]['timeout']){
  523. window.clearInterval($._spritely.instances[el_id]['timeout']);
  524. }
  525. if ($._spritely.instances[el_id] && $._spritely.instances[el_id]['interval']) {
  526. window.clearInterval($._spritely.instances[el_id]['interval']);
  527. }
  528. delete $._spritely.instances[el_id]
  529. return this;
  530. }
  531. })
  532. })(jQuery);
  533. // Stop IE6 re-loading background images continuously
  534. try {
  535. document.execCommand("BackgroundImageCache", false, true);
  536. } catch(err) {}