jquery.stellar.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. ;(function($, window, document, undefined) {
  2. 'use strict';
  3. var pluginName = 'stellar',
  4. defaults = {
  5. scrollProperty: 'scroll',
  6. positionProperty: 'position',
  7. horizontalScrolling: true,
  8. verticalScrolling: true,
  9. horizontalOffset: 0,
  10. verticalOffset: 0,
  11. responsive: false,
  12. parallaxBackgrounds: true,
  13. parallaxElements: true,
  14. hideDistantElements: true,
  15. hideElement: function($elem) { $elem.hide(); },
  16. showElement: function($elem) { $elem.show(); }
  17. },
  18. scrollProperty = {
  19. scroll: {
  20. getLeft: function($elem) { return $elem.scrollLeft(); },
  21. setLeft: function($elem, val) { $elem.scrollLeft(val); },
  22. getTop: function($elem) { return $elem.scrollTop(); },
  23. setTop: function($elem, val) { $elem.scrollTop(val); }
  24. },
  25. position: {
  26. getLeft: function($elem) { return parseInt($elem.css('left'), 10) * -1; },
  27. getTop: function($elem) { return parseInt($elem.css('top'), 10) * -1; }
  28. },
  29. margin: {
  30. getLeft: function($elem) { return parseInt($elem.css('margin-left'), 10) * -1; },
  31. getTop: function($elem) { return parseInt($elem.css('margin-top'), 10) * -1; }
  32. },
  33. transform: {
  34. getLeft: function($elem) {
  35. var computedTransform = getComputedStyle($elem[0])[prefixedTransform];
  36. return (computedTransform !== 'none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[4], 10) * -1 : 0);
  37. },
  38. getTop: function($elem) {
  39. var computedTransform = getComputedStyle($elem[0])[prefixedTransform];
  40. return (computedTransform !== 'none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[5], 10) * -1 : 0);
  41. }
  42. }
  43. },
  44. positionProperty = {
  45. position: {
  46. setLeft: function($elem, left) { $elem.css('left', left); },
  47. setTop: function($elem, top) { $elem.css('top', top); }
  48. },
  49. transform: {
  50. setPosition: function($elem, left, startingLeft, top, startingTop) {
  51. $elem[0].style[prefixedTransform] = 'translate3d(' + (left - startingLeft) + 'px, ' + (top - startingTop) + 'px, 0)';
  52. }
  53. }
  54. },
  55. // Returns a function which adds a vendor prefix to any CSS property name
  56. vendorPrefix = (function() {
  57. var prefixes = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/,
  58. style = $('script')[0].style,
  59. prefix = '',
  60. prop;
  61. for (prop in style) {
  62. if (prefixes.test(prop)) {
  63. prefix = prop.match(prefixes)[0];
  64. break;
  65. }
  66. }
  67. if ('WebkitOpacity' in style) { prefix = 'Webkit'; }
  68. if ('KhtmlOpacity' in style) { prefix = 'Khtml'; }
  69. return function(property) {
  70. return prefix + (prefix.length > 0 ? property.charAt(0).toUpperCase() + property.slice(1) : property);
  71. };
  72. }()),
  73. prefixedTransform = vendorPrefix('transform'),
  74. supportsBackgroundPositionXY = $('<div />', { style: 'background:#fff' }).css('background-position-x') !== undefined,
  75. setBackgroundPosition = (supportsBackgroundPositionXY ?
  76. function($elem, x, y) {
  77. $elem.css({
  78. 'background-position-x': x,
  79. 'background-position-y': y
  80. });
  81. } :
  82. function($elem, x, y) {
  83. $elem.css('background-position', x + ' ' + y);
  84. }
  85. ),
  86. getBackgroundPosition = (supportsBackgroundPositionXY ?
  87. function($elem) {
  88. return [
  89. $elem.css('background-position-x'),
  90. $elem.css('background-position-y')
  91. ];
  92. } :
  93. function($elem) {
  94. return $elem.css('background-position').split(' ');
  95. }
  96. ),
  97. requestAnimFrame = (
  98. window.requestAnimationFrame ||
  99. window.webkitRequestAnimationFrame ||
  100. window.mozRequestAnimationFrame ||
  101. window.oRequestAnimationFrame ||
  102. window.msRequestAnimationFrame ||
  103. function(callback) {
  104. setTimeout(callback, 1000 / 60);
  105. }
  106. );
  107. function Plugin(element, options) {
  108. this.element = element;
  109. this.options = $.extend({}, defaults, options);
  110. this._defaults = defaults;
  111. this._name = pluginName;
  112. this.init();
  113. }
  114. Plugin.prototype = {
  115. init: function() {
  116. this.options.name = pluginName + '_' + Math.floor(Math.random() * 1e9);
  117. this._defineElements();
  118. this._defineGetters();
  119. this._defineSetters();
  120. this._handleWindowLoadAndResize();
  121. this._detectViewport();
  122. this.refresh({ firstLoad: true });
  123. if (this.options.scrollProperty === 'scroll') {
  124. this._handleScrollEvent();
  125. } else {
  126. this._startAnimationLoop();
  127. }
  128. },
  129. _defineElements: function() {
  130. if (this.element === document.body) this.element = window;
  131. this.$scrollElement = $(this.element);
  132. this.$element = (this.element === window ? $('body') : this.$scrollElement);
  133. this.$viewportElement = (this.options.viewportElement !== undefined ? $(this.options.viewportElement) : (this.$scrollElement[0] === window || this.options.scrollProperty === 'scroll' ? this.$scrollElement : this.$scrollElement.parent()) );
  134. },
  135. _defineGetters: function() {
  136. var self = this,
  137. scrollPropertyAdapter = scrollProperty[self.options.scrollProperty];
  138. this._getScrollLeft = function() {
  139. return scrollPropertyAdapter.getLeft(self.$scrollElement);
  140. };
  141. this._getScrollTop = function() {
  142. return scrollPropertyAdapter.getTop(self.$scrollElement);
  143. };
  144. },
  145. _defineSetters: function() {
  146. var self = this,
  147. scrollPropertyAdapter = scrollProperty[self.options.scrollProperty],
  148. positionPropertyAdapter = positionProperty[self.options.positionProperty],
  149. setScrollLeft = scrollPropertyAdapter.setLeft,
  150. setScrollTop = scrollPropertyAdapter.setTop;
  151. this._setScrollLeft = (typeof setScrollLeft === 'function' ? function(val) {
  152. setScrollLeft(self.$scrollElement, val);
  153. } : $.noop);
  154. this._setScrollTop = (typeof setScrollTop === 'function' ? function(val) {
  155. setScrollTop(self.$scrollElement, val);
  156. } : $.noop);
  157. this._setPosition = positionPropertyAdapter.setPosition ||
  158. function($elem, left, startingLeft, top, startingTop) {
  159. if (self.options.horizontalScrolling) {
  160. positionPropertyAdapter.setLeft($elem, left, startingLeft);
  161. }
  162. if (self.options.verticalScrolling) {
  163. positionPropertyAdapter.setTop($elem, top, startingTop);
  164. }
  165. };
  166. },
  167. _handleWindowLoadAndResize: function() {
  168. var self = this,
  169. $window = $(window);
  170. if (self.options.responsive) {
  171. $window.bind('load.' + this.name, function() {
  172. self.refresh();
  173. });
  174. }
  175. $window.bind('resize.' + this.name, function() {
  176. self._detectViewport();
  177. if (self.options.responsive) {
  178. self.refresh();
  179. }
  180. });
  181. },
  182. refresh: function(options) {
  183. var self = this,
  184. oldLeft = self._getScrollLeft(),
  185. oldTop = self._getScrollTop();
  186. if (!options || !options.firstLoad) {
  187. this._reset();
  188. }
  189. this._setScrollLeft(0);
  190. this._setScrollTop(0);
  191. this._setOffsets();
  192. this._findParticles();
  193. this._findBackgrounds();
  194. // Fix for WebKit background rendering bug
  195. if (options && options.firstLoad && /WebKit/.test(navigator.userAgent)) {
  196. $(window).load(function() {
  197. var oldLeft = self._getScrollLeft(),
  198. oldTop = self._getScrollTop();
  199. self._setScrollLeft(oldLeft + 1);
  200. self._setScrollTop(oldTop + 1);
  201. self._setScrollLeft(oldLeft);
  202. self._setScrollTop(oldTop);
  203. });
  204. }
  205. this._setScrollLeft(oldLeft);
  206. this._setScrollTop(oldTop);
  207. },
  208. _detectViewport: function() {
  209. var viewportOffsets = this.$viewportElement.offset(),
  210. hasOffsets = viewportOffsets !== null && viewportOffsets !== undefined;
  211. this.viewportWidth = this.$viewportElement.width();
  212. this.viewportHeight = this.$viewportElement.height();
  213. this.viewportOffsetTop = (hasOffsets ? viewportOffsets.top : 0);
  214. this.viewportOffsetLeft = (hasOffsets ? viewportOffsets.left : 0);
  215. },
  216. _findParticles: function() {
  217. var self = this,
  218. scrollLeft = this._getScrollLeft(),
  219. scrollTop = this._getScrollTop();
  220. if (this.particles !== undefined) {
  221. for (var i = this.particles.length - 1; i >= 0; i--) {
  222. this.particles[i].$element.data('stellar-elementIsActive', undefined);
  223. }
  224. }
  225. this.particles = [];
  226. if (!this.options.parallaxElements) return;
  227. this.$element.find('[data-stellar-ratio]').each(function(i) {
  228. var $this = $(this),
  229. horizontalOffset,
  230. verticalOffset,
  231. positionLeft,
  232. positionTop,
  233. marginLeft,
  234. marginTop,
  235. $offsetParent,
  236. offsetLeft,
  237. offsetTop,
  238. parentOffsetLeft = 0,
  239. parentOffsetTop = 0,
  240. tempParentOffsetLeft = 0,
  241. tempParentOffsetTop = 0;
  242. // Ensure this element isn't already part of another scrolling element
  243. if (!$this.data('stellar-elementIsActive')) {
  244. $this.data('stellar-elementIsActive', this);
  245. } else if ($this.data('stellar-elementIsActive') !== this) {
  246. return;
  247. }
  248. self.options.showElement($this);
  249. // Save/restore the original top and left CSS values in case we refresh the particles or destroy the instance
  250. if (!$this.data('stellar-startingLeft')) {
  251. $this.data('stellar-startingLeft', $this.css('left'));
  252. $this.data('stellar-startingTop', $this.css('top'));
  253. } else {
  254. $this.css('left', $this.data('stellar-startingLeft'));
  255. $this.css('top', $this.data('stellar-startingTop'));
  256. }
  257. positionLeft = $this.position().left;
  258. positionTop = $this.position().top;
  259. // Catch-all for margin top/left properties (these evaluate to 'auto' in IE7 and IE8)
  260. marginLeft = ($this.css('margin-left') === 'auto') ? 0 : parseInt($this.css('margin-left'), 10);
  261. marginTop = ($this.css('margin-top') === 'auto') ? 0 : parseInt($this.css('margin-top'), 10);
  262. offsetLeft = $this.offset().left - marginLeft;
  263. offsetTop = $this.offset().top - marginTop;
  264. // Calculate the offset parent
  265. $this.parents().each(function() {
  266. var $this = $(this);
  267. if ($this.data('stellar-offset-parent') === true) {
  268. parentOffsetLeft = tempParentOffsetLeft;
  269. parentOffsetTop = tempParentOffsetTop;
  270. $offsetParent = $this;
  271. return false;
  272. } else {
  273. tempParentOffsetLeft += $this.position().left;
  274. tempParentOffsetTop += $this.position().top;
  275. }
  276. });
  277. // Detect the offsets
  278. horizontalOffset = ($this.data('stellar-horizontal-offset') !== undefined ? $this.data('stellar-horizontal-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-horizontal-offset') !== undefined ? $offsetParent.data('stellar-horizontal-offset') : self.horizontalOffset));
  279. verticalOffset = ($this.data('stellar-vertical-offset') !== undefined ? $this.data('stellar-vertical-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-vertical-offset') !== undefined ? $offsetParent.data('stellar-vertical-offset') : self.verticalOffset));
  280. // Add our object to the particles collection
  281. self.particles.push({
  282. $element: $this,
  283. $offsetParent: $offsetParent,
  284. isFixed: $this.css('position') === 'fixed',
  285. horizontalOffset: horizontalOffset,
  286. verticalOffset: verticalOffset,
  287. startingPositionLeft: positionLeft,
  288. startingPositionTop: positionTop,
  289. startingOffsetLeft: offsetLeft,
  290. startingOffsetTop: offsetTop,
  291. parentOffsetLeft: parentOffsetLeft,
  292. parentOffsetTop: parentOffsetTop,
  293. stellarRatio: ($this.data('stellar-ratio') !== undefined ? $this.data('stellar-ratio') : 1),
  294. width: $this.outerWidth(true),
  295. height: $this.outerHeight(true),
  296. isHidden: false
  297. });
  298. });
  299. },
  300. _findBackgrounds: function() {
  301. var self = this,
  302. scrollLeft = this._getScrollLeft(),
  303. scrollTop = this._getScrollTop(),
  304. $backgroundElements;
  305. this.backgrounds = [];
  306. if (!this.options.parallaxBackgrounds) return;
  307. $backgroundElements = this.$element.find('[data-stellar-background-ratio]');
  308. if (this.$element.data('stellar-background-ratio')) {
  309. $backgroundElements = $backgroundElements.add(this.$element);
  310. }
  311. $backgroundElements.each(function() {
  312. var $this = $(this),
  313. backgroundPosition = getBackgroundPosition($this),
  314. horizontalOffset,
  315. verticalOffset,
  316. positionLeft,
  317. positionTop,
  318. marginLeft,
  319. marginTop,
  320. offsetLeft,
  321. offsetTop,
  322. $offsetParent,
  323. parentOffsetLeft = 0,
  324. parentOffsetTop = 0,
  325. tempParentOffsetLeft = 0,
  326. tempParentOffsetTop = 0;
  327. // Ensure this element isn't already part of another scrolling element
  328. if (!$this.data('stellar-backgroundIsActive')) {
  329. $this.data('stellar-backgroundIsActive', this);
  330. } else if ($this.data('stellar-backgroundIsActive') !== this) {
  331. return;
  332. }
  333. // Save/restore the original top and left CSS values in case we destroy the instance
  334. if (!$this.data('stellar-backgroundStartingLeft')) {
  335. $this.data('stellar-backgroundStartingLeft', backgroundPosition[0]);
  336. $this.data('stellar-backgroundStartingTop', backgroundPosition[1]);
  337. } else {
  338. setBackgroundPosition($this, $this.data('stellar-backgroundStartingLeft'), $this.data('stellar-backgroundStartingTop'));
  339. }
  340. // Catch-all for margin top/left properties (these evaluate to 'auto' in IE7 and IE8)
  341. marginLeft = ($this.css('margin-left') === 'auto') ? 0 : parseInt($this.css('margin-left'), 10);
  342. marginTop = ($this.css('margin-top') === 'auto') ? 0 : parseInt($this.css('margin-top'), 10);
  343. offsetLeft = $this.offset().left - marginLeft - scrollLeft;
  344. offsetTop = $this.offset().top - marginTop - scrollTop;
  345. // Calculate the offset parent
  346. $this.parents().each(function() {
  347. var $this = $(this);
  348. if ($this.data('stellar-offset-parent') === true) {
  349. parentOffsetLeft = tempParentOffsetLeft;
  350. parentOffsetTop = tempParentOffsetTop;
  351. $offsetParent = $this;
  352. return false;
  353. } else {
  354. tempParentOffsetLeft += $this.position().left;
  355. tempParentOffsetTop += $this.position().top;
  356. }
  357. });
  358. // Detect the offsets
  359. horizontalOffset = ($this.data('stellar-horizontal-offset') !== undefined ? $this.data('stellar-horizontal-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-horizontal-offset') !== undefined ? $offsetParent.data('stellar-horizontal-offset') : self.horizontalOffset));
  360. verticalOffset = ($this.data('stellar-vertical-offset') !== undefined ? $this.data('stellar-vertical-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-vertical-offset') !== undefined ? $offsetParent.data('stellar-vertical-offset') : self.verticalOffset));
  361. self.backgrounds.push({
  362. $element: $this,
  363. $offsetParent: $offsetParent,
  364. isFixed: $this.css('background-attachment') === 'fixed',
  365. horizontalOffset: horizontalOffset,
  366. verticalOffset: verticalOffset,
  367. startingValueLeft: backgroundPosition[0],
  368. startingValueTop: backgroundPosition[1],
  369. startingBackgroundPositionLeft: (isNaN(parseInt(backgroundPosition[0], 10)) ? 0 : parseInt(backgroundPosition[0], 10)),
  370. startingBackgroundPositionTop: (isNaN(parseInt(backgroundPosition[1], 10)) ? 0 : parseInt(backgroundPosition[1], 10)),
  371. startingPositionLeft: $this.position().left,
  372. startingPositionTop: $this.position().top,
  373. startingOffsetLeft: offsetLeft,
  374. startingOffsetTop: offsetTop,
  375. parentOffsetLeft: parentOffsetLeft,
  376. parentOffsetTop: parentOffsetTop,
  377. stellarRatio: ($this.data('stellar-background-ratio') === undefined ? 1 : $this.data('stellar-background-ratio'))
  378. });
  379. });
  380. },
  381. _reset: function() {
  382. var particle,
  383. startingPositionLeft,
  384. startingPositionTop,
  385. background,
  386. i;
  387. for (i = this.particles.length - 1; i >= 0; i--) {
  388. particle = this.particles[i];
  389. startingPositionLeft = particle.$element.data('stellar-startingLeft');
  390. startingPositionTop = particle.$element.data('stellar-startingTop');
  391. this._setPosition(particle.$element, startingPositionLeft, startingPositionLeft, startingPositionTop, startingPositionTop);
  392. this.options.showElement(particle.$element);
  393. particle.$element.data('stellar-startingLeft', null).data('stellar-elementIsActive', null).data('stellar-backgroundIsActive', null);
  394. }
  395. for (i = this.backgrounds.length - 1; i >= 0; i--) {
  396. background = this.backgrounds[i];
  397. background.$element.data('stellar-backgroundStartingLeft', null).data('stellar-backgroundStartingTop', null);
  398. setBackgroundPosition(background.$element, background.startingValueLeft, background.startingValueTop);
  399. }
  400. },
  401. destroy: function() {
  402. this._reset();
  403. this.$scrollElement.unbind('resize.' + this.name).unbind('scroll.' + this.name);
  404. this._animationLoop = $.noop;
  405. $(window).unbind('load.' + this.name).unbind('resize.' + this.name);
  406. },
  407. _setOffsets: function() {
  408. var self = this,
  409. $window = $(window);
  410. $window.unbind('resize.horizontal-' + this.name).unbind('resize.vertical-' + this.name);
  411. if (typeof this.options.horizontalOffset === 'function') {
  412. this.horizontalOffset = this.options.horizontalOffset();
  413. $window.bind('resize.horizontal-' + this.name, function() {
  414. self.horizontalOffset = self.options.horizontalOffset();
  415. });
  416. } else {
  417. this.horizontalOffset = this.options.horizontalOffset;
  418. }
  419. if (typeof this.options.verticalOffset === 'function') {
  420. this.verticalOffset = this.options.verticalOffset();
  421. $window.bind('resize.vertical-' + this.name, function() {
  422. self.verticalOffset = self.options.verticalOffset();
  423. });
  424. } else {
  425. this.verticalOffset = this.options.verticalOffset;
  426. }
  427. },
  428. _repositionElements: function() {
  429. var scrollLeft = this._getScrollLeft(),
  430. scrollTop = this._getScrollTop(),
  431. horizontalOffset,
  432. verticalOffset,
  433. particle,
  434. fixedRatioOffset,
  435. background,
  436. bgLeft,
  437. bgTop,
  438. isVisibleVertical = true,
  439. isVisibleHorizontal = true,
  440. newPositionLeft,
  441. newPositionTop,
  442. newOffsetLeft,
  443. newOffsetTop,
  444. i;
  445. // First check that the scroll position or container size has changed
  446. if (this.currentScrollLeft === scrollLeft && this.currentScrollTop === scrollTop && this.currentWidth === this.viewportWidth && this.currentHeight === this.viewportHeight) {
  447. return;
  448. } else {
  449. this.currentScrollLeft = scrollLeft;
  450. this.currentScrollTop = scrollTop;
  451. this.currentWidth = this.viewportWidth;
  452. this.currentHeight = this.viewportHeight;
  453. }
  454. // Reposition elements
  455. for (i = this.particles.length - 1; i >= 0; i--) {
  456. particle = this.particles[i];
  457. fixedRatioOffset = (particle.isFixed ? 1 : 0);
  458. // Calculate position, then calculate what the particle's new offset will be (for visibility check)
  459. if (this.options.horizontalScrolling) {
  460. newPositionLeft = (scrollLeft + particle.horizontalOffset + this.viewportOffsetLeft + particle.startingPositionLeft - particle.startingOffsetLeft + particle.parentOffsetLeft) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionLeft;
  461. newOffsetLeft = newPositionLeft - particle.startingPositionLeft + particle.startingOffsetLeft;
  462. } else {
  463. newPositionLeft = particle.startingPositionLeft;
  464. newOffsetLeft = particle.startingOffsetLeft;
  465. }
  466. if (this.options.verticalScrolling) {
  467. newPositionTop = (scrollTop + particle.verticalOffset + this.viewportOffsetTop + particle.startingPositionTop - particle.startingOffsetTop + particle.parentOffsetTop) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionTop;
  468. newOffsetTop = newPositionTop - particle.startingPositionTop + particle.startingOffsetTop;
  469. } else {
  470. newPositionTop = particle.startingPositionTop;
  471. newOffsetTop = particle.startingOffsetTop;
  472. }
  473. // Check visibility
  474. if (this.options.hideDistantElements) {
  475. isVisibleHorizontal = !this.options.horizontalScrolling || newOffsetLeft + particle.width > (particle.isFixed ? 0 : scrollLeft) && newOffsetLeft < (particle.isFixed ? 0 : scrollLeft) + this.viewportWidth + this.viewportOffsetLeft;
  476. isVisibleVertical = !this.options.verticalScrolling || newOffsetTop + particle.height > (particle.isFixed ? 0 : scrollTop) && newOffsetTop < (particle.isFixed ? 0 : scrollTop) + this.viewportHeight + this.viewportOffsetTop;
  477. }
  478. if (isVisibleHorizontal && isVisibleVertical) {
  479. if (particle.isHidden) {
  480. this.options.showElement(particle.$element);
  481. particle.isHidden = false;
  482. }
  483. this._setPosition(particle.$element, newPositionLeft, particle.startingPositionLeft, newPositionTop, particle.startingPositionTop);
  484. } else {
  485. if (!particle.isHidden) {
  486. this.options.hideElement(particle.$element);
  487. particle.isHidden = true;
  488. }
  489. }
  490. }
  491. // Reposition backgrounds
  492. for (i = this.backgrounds.length - 1; i >= 0; i--) {
  493. background = this.backgrounds[i];
  494. fixedRatioOffset = (background.isFixed ? 0 : 1);
  495. bgLeft = (this.options.horizontalScrolling ? (scrollLeft + background.horizontalOffset - this.viewportOffsetLeft - background.startingOffsetLeft + background.parentOffsetLeft - background.startingBackgroundPositionLeft) * (fixedRatioOffset - background.stellarRatio) + 'px' : background.startingValueLeft);
  496. bgTop = (this.options.verticalScrolling ? (scrollTop + background.verticalOffset - this.viewportOffsetTop - background.startingOffsetTop + background.parentOffsetTop - background.startingBackgroundPositionTop) * (fixedRatioOffset - background.stellarRatio) + 'px' : background.startingValueTop);
  497. setBackgroundPosition(background.$element, bgLeft, bgTop);
  498. }
  499. },
  500. _handleScrollEvent: function() {
  501. var self = this,
  502. ticking = false;
  503. var update = function() {
  504. self._repositionElements();
  505. ticking = false;
  506. };
  507. var requestTick = function() {
  508. if (!ticking) {
  509. requestAnimFrame(update);
  510. ticking = true;
  511. }
  512. };
  513. this.$scrollElement.bind('scroll.' + this.name, requestTick);
  514. requestTick();
  515. },
  516. _startAnimationLoop: function() {
  517. var self = this;
  518. this._animationLoop = function() {
  519. requestAnimFrame(self._animationLoop);
  520. self._repositionElements();
  521. };
  522. this._animationLoop();
  523. }
  524. };
  525. $.fn[pluginName] = function (options) {
  526. var args = arguments;
  527. if (options === undefined || typeof options === 'object') {
  528. return this.each(function () {
  529. if (!$.data(this, 'plugin_' + pluginName)) {
  530. $.data(this, 'plugin_' + pluginName, new Plugin(this, options));
  531. }
  532. });
  533. } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
  534. return this.each(function () {
  535. var instance = $.data(this, 'plugin_' + pluginName);
  536. if (instance instanceof Plugin && typeof instance[options] === 'function') {
  537. instance[options].apply(instance, Array.prototype.slice.call(args, 1));
  538. }
  539. if (options === 'destroy') {
  540. $.data(this, 'plugin_' + pluginName, null);
  541. }
  542. });
  543. }
  544. };
  545. $[pluginName] = function(options) {
  546. var $window = $(window);
  547. return $window.stellar.apply($window, Array.prototype.slice.call(arguments, 0));
  548. };
  549. // Expose the scroll and position property function hashes so they can be extended
  550. $[pluginName].scrollProperty = scrollProperty;
  551. $[pluginName].positionProperty = positionProperty;
  552. // Expose the plugin class so it can be modified
  553. window.Stellar = Plugin;
  554. }(jQuery, this, document));