jquery.stellar.js 23 KB

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