parallax.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. /*!
  2. * parallax.js v1.5.0 (http://pixelcog.github.io/parallax.js/)
  3. * @copyright 2016 PixelCog, Inc.
  4. * @license MIT (https://github.com/pixelcog/parallax.js/blob/master/LICENSE)
  5. */
  6. ;(function ( $, window, document, undefined ) {
  7. // Polyfill for requestAnimationFrame
  8. // via: https://gist.github.com/paulirish/1579671
  9. (function() {
  10. var lastTime = 0;
  11. var vendors = ['ms', 'moz', 'webkit', 'o'];
  12. for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
  13. window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
  14. window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
  15. }
  16. if (!window.requestAnimationFrame)
  17. window.requestAnimationFrame = function(callback) {
  18. var currTime = new Date().getTime();
  19. var timeToCall = Math.max(0, 16 - (currTime - lastTime));
  20. var id = window.setTimeout(function() { callback(currTime + timeToCall); },
  21. timeToCall);
  22. lastTime = currTime + timeToCall;
  23. return id;
  24. };
  25. if (!window.cancelAnimationFrame)
  26. window.cancelAnimationFrame = function(id) {
  27. clearTimeout(id);
  28. };
  29. }());
  30. // Parallax Constructor
  31. function Parallax(element, options) {
  32. var self = this;
  33. if (typeof options == 'object') {
  34. delete options.refresh;
  35. delete options.render;
  36. $.extend(this, options);
  37. }
  38. this.$element = $(element);
  39. if (!this.imageSrc && this.$element.is('img')) {
  40. this.imageSrc = this.$element.attr('src');
  41. }
  42. var positions = (this.position + '').toLowerCase().match(/\S+/g) || [];
  43. if (positions.length < 1) {
  44. positions.push('center');
  45. }
  46. if (positions.length == 1) {
  47. positions.push(positions[0]);
  48. }
  49. if (positions[0] == 'top' || positions[0] == 'bottom' || positions[1] == 'left' || positions[1] == 'right') {
  50. positions = [positions[1], positions[0]];
  51. }
  52. if (this.positionX !== undefined) positions[0] = this.positionX.toLowerCase();
  53. if (this.positionY !== undefined) positions[1] = this.positionY.toLowerCase();
  54. self.positionX = positions[0];
  55. self.positionY = positions[1];
  56. if (this.positionX != 'left' && this.positionX != 'right') {
  57. if (isNaN(parseInt(this.positionX))) {
  58. this.positionX = 'center';
  59. } else {
  60. this.positionX = parseInt(this.positionX);
  61. }
  62. }
  63. if (this.positionY != 'top' && this.positionY != 'bottom') {
  64. if (isNaN(parseInt(this.positionY))) {
  65. this.positionY = 'center';
  66. } else {
  67. this.positionY = parseInt(this.positionY);
  68. }
  69. }
  70. this.position =
  71. this.positionX + (isNaN(this.positionX)? '' : 'px') + ' ' +
  72. this.positionY + (isNaN(this.positionY)? '' : 'px');
  73. if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
  74. if (this.imageSrc && this.iosFix && !this.$element.is('img')) {
  75. this.$element.css({
  76. backgroundImage: 'url(' + this.imageSrc + ')',
  77. backgroundSize: 'cover',
  78. backgroundPosition: this.position
  79. });
  80. }
  81. return this;
  82. }
  83. if (navigator.userAgent.match(/(Android)/)) {
  84. if (this.imageSrc && this.androidFix && !this.$element.is('img')) {
  85. this.$element.css({
  86. backgroundImage: 'url(' + this.imageSrc + ')',
  87. backgroundSize: 'cover',
  88. backgroundPosition: this.position
  89. });
  90. }
  91. return this;
  92. }
  93. this.$mirror = $('<div />').prependTo(this.mirrorContainer);
  94. var slider = this.$element.find('>.parallax-slider');
  95. var sliderExisted = false;
  96. if (slider.length == 0)
  97. this.$slider = $('<img />').prependTo(this.$mirror);
  98. else {
  99. this.$slider = slider.prependTo(this.$mirror)
  100. sliderExisted = true;
  101. }
  102. this.$mirror.addClass('parallax-mirror').css({
  103. visibility: 'hidden',
  104. zIndex: this.zIndex,
  105. position: 'fixed',
  106. top: 0,
  107. left: 0,
  108. overflow: 'hidden'
  109. });
  110. this.$slider.addClass('parallax-slider').one('load', function() {
  111. if (!self.naturalHeight || !self.naturalWidth) {
  112. self.naturalHeight = this.naturalHeight || this.height || 1;
  113. self.naturalWidth = this.naturalWidth || this.width || 1;
  114. }
  115. self.aspectRatio = self.naturalWidth / self.naturalHeight;
  116. Parallax.isSetup || Parallax.setup();
  117. Parallax.sliders.push(self);
  118. Parallax.isFresh = false;
  119. Parallax.requestRender();
  120. });
  121. if (!sliderExisted)
  122. this.$slider[0].src = this.imageSrc;
  123. if (this.naturalHeight && this.naturalWidth || this.$slider[0].complete || slider.length > 0) {
  124. this.$slider.trigger('load');
  125. }
  126. }
  127. // Parallax Instance Methods
  128. $.extend(Parallax.prototype, {
  129. speed: 0.2,
  130. bleed: 0,
  131. zIndex: -100,
  132. iosFix: true,
  133. androidFix: true,
  134. position: 'center',
  135. overScrollFix: false,
  136. mirrorContainer: 'body',
  137. refresh: function() {
  138. this.boxWidth = this.$element.outerWidth();
  139. this.boxHeight = this.$element.outerHeight() + this.bleed * 2;
  140. this.boxOffsetTop = this.$element.offset().top - this.bleed;
  141. this.boxOffsetLeft = this.$element.offset().left;
  142. this.boxOffsetBottom = this.boxOffsetTop + this.boxHeight;
  143. var winHeight = Parallax.winHeight;
  144. var docHeight = Parallax.docHeight;
  145. var maxOffset = Math.min(this.boxOffsetTop, docHeight - winHeight);
  146. var minOffset = Math.max(this.boxOffsetTop + this.boxHeight - winHeight, 0);
  147. var imageHeightMin = this.boxHeight + (maxOffset - minOffset) * (1 - this.speed) | 0;
  148. var imageOffsetMin = (this.boxOffsetTop - maxOffset) * (1 - this.speed) | 0;
  149. var margin;
  150. if (imageHeightMin * this.aspectRatio >= this.boxWidth) {
  151. this.imageWidth = imageHeightMin * this.aspectRatio | 0;
  152. this.imageHeight = imageHeightMin;
  153. this.offsetBaseTop = imageOffsetMin;
  154. margin = this.imageWidth - this.boxWidth;
  155. if (this.positionX == 'left') {
  156. this.offsetLeft = 0;
  157. } else if (this.positionX == 'right') {
  158. this.offsetLeft = - margin;
  159. } else if (!isNaN(this.positionX)) {
  160. this.offsetLeft = Math.max(this.positionX, - margin);
  161. } else {
  162. this.offsetLeft = - margin / 2 | 0;
  163. }
  164. } else {
  165. this.imageWidth = this.boxWidth;
  166. this.imageHeight = this.boxWidth / this.aspectRatio | 0;
  167. this.offsetLeft = 0;
  168. margin = this.imageHeight - imageHeightMin;
  169. if (this.positionY == 'top') {
  170. this.offsetBaseTop = imageOffsetMin;
  171. } else if (this.positionY == 'bottom') {
  172. this.offsetBaseTop = imageOffsetMin - margin;
  173. } else if (!isNaN(this.positionY)) {
  174. this.offsetBaseTop = imageOffsetMin + Math.max(this.positionY, - margin);
  175. } else {
  176. this.offsetBaseTop = imageOffsetMin - margin / 2 | 0;
  177. }
  178. }
  179. },
  180. render: function() {
  181. var scrollTop = Parallax.scrollTop;
  182. var scrollLeft = Parallax.scrollLeft;
  183. var overScroll = this.overScrollFix ? Parallax.overScroll : 0;
  184. var scrollBottom = scrollTop + Parallax.winHeight;
  185. if (this.boxOffsetBottom > scrollTop && this.boxOffsetTop <= scrollBottom) {
  186. this.visibility = 'visible';
  187. this.mirrorTop = this.boxOffsetTop - scrollTop;
  188. this.mirrorLeft = this.boxOffsetLeft - scrollLeft;
  189. this.offsetTop = this.offsetBaseTop - this.mirrorTop * (1 - this.speed);
  190. } else {
  191. this.visibility = 'hidden';
  192. }
  193. this.$mirror.css({
  194. transform: 'translate3d('+this.mirrorLeft+'px, '+(this.mirrorTop - overScroll)+'px, 0px)',
  195. visibility: this.visibility,
  196. height: this.boxHeight,
  197. width: this.boxWidth
  198. });
  199. this.$slider.css({
  200. transform: 'translate3d('+this.offsetLeft+'px, '+this.offsetTop+'px, 0px)',
  201. position: 'absolute',
  202. height: this.imageHeight,
  203. width: this.imageWidth,
  204. maxWidth: 'none'
  205. });
  206. }
  207. });
  208. // Parallax Static Methods
  209. $.extend(Parallax, {
  210. scrollTop: 0,
  211. scrollLeft: 0,
  212. winHeight: 0,
  213. winWidth: 0,
  214. docHeight: 1 << 30,
  215. docWidth: 1 << 30,
  216. sliders: [],
  217. isReady: false,
  218. isFresh: false,
  219. isBusy: false,
  220. setup: function() {
  221. if (this.isReady) return;
  222. var self = this;
  223. var $doc = $(document), $win = $(window);
  224. var loadDimensions = function() {
  225. Parallax.winHeight = $win.height();
  226. Parallax.winWidth = $win.width();
  227. Parallax.docHeight = $doc.height();
  228. Parallax.docWidth = $doc.width();
  229. };
  230. var loadScrollPosition = function() {
  231. var winScrollTop = $win.scrollTop();
  232. var scrollTopMax = Parallax.docHeight - Parallax.winHeight;
  233. var scrollLeftMax = Parallax.docWidth - Parallax.winWidth;
  234. Parallax.scrollTop = Math.max(0, Math.min(scrollTopMax, winScrollTop));
  235. Parallax.scrollLeft = Math.max(0, Math.min(scrollLeftMax, $win.scrollLeft()));
  236. Parallax.overScroll = Math.max(winScrollTop - scrollTopMax, Math.min(winScrollTop, 0));
  237. };
  238. $win.on('resize.px.parallax load.px.parallax', function() {
  239. loadDimensions();
  240. self.refresh();
  241. Parallax.isFresh = false;
  242. Parallax.requestRender();
  243. })
  244. .on('scroll.px.parallax load.px.parallax', function() {
  245. loadScrollPosition();
  246. Parallax.requestRender();
  247. });
  248. loadDimensions();
  249. loadScrollPosition();
  250. this.isReady = true;
  251. var lastPosition = -1;
  252. function frameLoop() {
  253. if (lastPosition == window.pageYOffset) { // Avoid overcalculations
  254. window.requestAnimationFrame(frameLoop);
  255. return false;
  256. } else lastPosition = window.pageYOffset;
  257. self.render();
  258. window.requestAnimationFrame(frameLoop);
  259. }
  260. frameLoop();
  261. },
  262. configure: function(options) {
  263. if (typeof options == 'object') {
  264. delete options.refresh;
  265. delete options.render;
  266. $.extend(this.prototype, options);
  267. }
  268. },
  269. refresh: function() {
  270. $.each(this.sliders, function(){ this.refresh(); });
  271. this.isFresh = true;
  272. },
  273. render: function() {
  274. this.isFresh || this.refresh();
  275. $.each(this.sliders, function(){ this.render(); });
  276. },
  277. requestRender: function() {
  278. var self = this;
  279. self.render();
  280. self.isBusy = false;
  281. },
  282. destroy: function(el){
  283. var i,
  284. parallaxElement = $(el).data('px.parallax');
  285. parallaxElement.$mirror.remove();
  286. for(i=0; i < this.sliders.length; i+=1){
  287. if(this.sliders[i] == parallaxElement){
  288. this.sliders.splice(i, 1);
  289. }
  290. }
  291. $(el).data('px.parallax', false);
  292. if(this.sliders.length === 0){
  293. $(window).off('scroll.px.parallax resize.px.parallax load.px.parallax');
  294. this.isReady = false;
  295. Parallax.isSetup = false;
  296. }
  297. }
  298. });
  299. // Parallax Plugin Definition
  300. function Plugin(option) {
  301. return this.each(function () {
  302. var $this = $(this);
  303. var options = typeof option == 'object' && option;
  304. if (this == window || this == document || $this.is('body')) {
  305. Parallax.configure(options);
  306. }
  307. else if (!$this.data('px.parallax')) {
  308. options = $.extend({}, $this.data(), options);
  309. $this.data('px.parallax', new Parallax(this, options));
  310. }
  311. else if (typeof option == 'object')
  312. {
  313. $.extend($this.data('px.parallax'), options);
  314. }
  315. if (typeof option == 'string') {
  316. if(option == 'destroy'){
  317. Parallax.destroy(this);
  318. }else{
  319. Parallax[option]();
  320. }
  321. }
  322. });
  323. }
  324. var old = $.fn.parallax;
  325. $.fn.parallax = Plugin;
  326. $.fn.parallax.Constructor = Parallax;
  327. // Parallax No Conflict
  328. $.fn.parallax.noConflict = function () {
  329. $.fn.parallax = old;
  330. return this;
  331. };
  332. // Parallax Data-API
  333. $( function () {
  334. $('[data-parallax="scroll"]').parallax();
  335. });
  336. }(jQuery, window, document));