tilt.jquery.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. (function (factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. // AMD. Register as an anonymous module.
  4. define(['jquery'], factory);
  5. } else if (typeof module === 'object' && module.exports) {
  6. // Node/CommonJS
  7. module.exports = function( root, jQuery ) {
  8. if ( jQuery === undefined ) {
  9. // require('jQuery') returns a factory that requires window to
  10. // build a jQuery instance, we normalize how we use modules
  11. // that require this pattern but the window provided is a noop
  12. // if it's defined (how jquery works)
  13. if ( typeof window !== 'undefined' ) {
  14. jQuery = require('jquery');
  15. }
  16. else {
  17. jQuery = require('jquery')(root);
  18. }
  19. }
  20. factory(jQuery);
  21. return jQuery;
  22. };
  23. } else {
  24. // Browser globals
  25. factory(jQuery);
  26. }
  27. }(function ($) {
  28. $.fn.tilt = function (options) {
  29. /**
  30. * RequestAnimationFrame
  31. */
  32. const requestTick = function() {
  33. if (this.ticking) return;
  34. requestAnimationFrame(updateTransforms.bind(this));
  35. this.ticking = true;
  36. };
  37. /**
  38. * Bind mouse movement evens on instance
  39. */
  40. const bindEvents = function() {
  41. const _this = this;
  42. $(this).on('mousemove', mouseMove);
  43. $(this).on('mouseenter', mouseEnter);
  44. if (this.settings.reset) $(this).on('mouseleave', mouseLeave);
  45. if (this.settings.glare) $(window).on('resize', updateGlareSize.bind(_this));
  46. };
  47. /**
  48. * Set transition only on mouse leave and mouse enter so it doesn't influence mouse move transforms
  49. */
  50. const setTransition = function() {
  51. if (this.timeout !== undefined) clearTimeout(this.timeout);
  52. $(this).css({'transition': `${this.settings.speed}ms ${this.settings.easing}`});
  53. if(this.settings.glare) this.glareElement.css({'transition': `opacity ${this.settings.speed}ms ${this.settings.easing}`});
  54. this.timeout = setTimeout(() => {
  55. $(this).css({'transition': ''});
  56. if(this.settings.glare) this.glareElement.css({'transition': ''});
  57. }, this.settings.speed);
  58. };
  59. /**
  60. * When user mouse enters tilt element
  61. */
  62. const mouseEnter = function(event) {
  63. this.ticking = false;
  64. $(this).css({'will-change': 'transform'});
  65. setTransition.call(this);
  66. // Trigger change event
  67. $(this).trigger("tilt.mouseEnter");
  68. };
  69. /**
  70. * Return the x,y position of the mouse on the tilt element
  71. * @returns {{x: *, y: *}}
  72. */
  73. const getMousePositions = function(event) {
  74. if (typeof(event) === "undefined") {
  75. event = {
  76. pageX: $(this).offset().left + $(this).outerWidth() / 2,
  77. pageY: $(this).offset().top + $(this).outerHeight() / 2
  78. };
  79. }
  80. return {x: event.pageX, y: event.pageY};
  81. };
  82. /**
  83. * When user mouse moves over the tilt element
  84. */
  85. const mouseMove = function(event) {
  86. this.mousePositions = getMousePositions(event);
  87. requestTick.call(this);
  88. };
  89. /**
  90. * When user mouse leaves tilt element
  91. */
  92. const mouseLeave = function() {
  93. setTransition.call(this);
  94. this.reset = true;
  95. requestTick.call(this);
  96. // Trigger change event
  97. $(this).trigger("tilt.mouseLeave");
  98. };
  99. /**
  100. * Get tilt values
  101. *
  102. * @returns {{x: tilt value, y: tilt value}}
  103. */
  104. const getValues = function() {
  105. const width = $(this).outerWidth();
  106. const height = $(this).outerHeight();
  107. const left = $(this).offset().left;
  108. const top = $(this).offset().top;
  109. const percentageX = (this.mousePositions.x - left) / width;
  110. const percentageY = (this.mousePositions.y - top) / height;
  111. // x or y position inside instance / width of instance = percentage of position inside instance * the max tilt value
  112. const tiltX = ((this.settings.maxTilt / 2) - ((percentageX) * this.settings.maxTilt)).toFixed(2);
  113. const tiltY = (((percentageY) * this.settings.maxTilt) - (this.settings.maxTilt / 2)).toFixed(2);
  114. // angle
  115. const angle = Math.atan2(this.mousePositions.x - (left+width/2),- (this.mousePositions.y - (top+height/2)) )*(180/Math.PI);
  116. // Return x & y tilt values
  117. return {tiltX, tiltY, 'percentageX': percentageX * 100, 'percentageY': percentageY * 100, angle};
  118. };
  119. /**
  120. * Update tilt transforms on mousemove
  121. */
  122. const updateTransforms = function() {
  123. this.transforms = getValues.call(this);
  124. if (this.reset) {
  125. this.reset = false;
  126. $(this).css('transform', `perspective(${this.settings.perspective}px) rotateX(0deg) rotateY(0deg)`);
  127. // Rotate glare if enabled
  128. if (this.settings.glare){
  129. this.glareElement.css('transform', `rotate(180deg) translate(-50%, -50%)`);
  130. this.glareElement.css('opacity', `0`);
  131. }
  132. return;
  133. } else {
  134. $(this).css('transform', `perspective(${this.settings.perspective}px) rotateX(${this.settings.disableAxis === 'x' ? 0 : this.transforms.tiltY}deg) rotateY(${this.settings.disableAxis === 'y' ? 0 : this.transforms.tiltX}deg) scale3d(${this.settings.scale},${this.settings.scale},${this.settings.scale})`);
  135. // Rotate glare if enabled
  136. if (this.settings.glare){
  137. this.glareElement.css('transform', `rotate(${this.transforms.angle}deg) translate(-50%, -50%)`);
  138. this.glareElement.css('opacity', `${this.transforms.percentageY * this.settings.maxGlare / 100}`);
  139. }
  140. }
  141. // Trigger change event
  142. $(this).trigger("change", [this.transforms]);
  143. this.ticking = false;
  144. };
  145. /**
  146. * Prepare elements
  147. */
  148. const prepareGlare = function () {
  149. const glarePrerender = this.settings.glarePrerender;
  150. // If option pre-render is enabled we assume all html/css is present for an optimal glare effect.
  151. if (!glarePrerender)
  152. // Create glare element
  153. $(this).append('<div class="js-tilt-glare"><div class="js-tilt-glare-inner"></div></div>');
  154. // Store glare selector if glare is enabled
  155. this.glareElementWrapper = $(this).find(".js-tilt-glare");
  156. this.glareElement = $(this).find(".js-tilt-glare-inner");
  157. // Remember? We assume all css is already set, so just return
  158. if (glarePrerender) return;
  159. // Abstracted re-usable glare styles
  160. const stretch = {
  161. 'position': 'absolute',
  162. 'top': '0',
  163. 'left': '0',
  164. 'width': '100%',
  165. 'height': '100%',
  166. };
  167. // Style glare wrapper
  168. this.glareElementWrapper.css(stretch).css({
  169. 'overflow': 'hidden',
  170. });
  171. // Style glare element
  172. this.glareElement.css({
  173. 'position': 'absolute',
  174. 'top': '50%',
  175. 'left': '50%',
  176. 'pointer-events': 'none',
  177. 'background-image': `linear-gradient(0deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 100%)`,
  178. 'width': `${$(this).outerWidth()*2}`,
  179. 'height': `${$(this).outerWidth()*2}`,
  180. 'transform': 'rotate(180deg) translate(-50%, -50%)',
  181. 'transform-origin': '0% 0%',
  182. 'opacity': '0',
  183. });
  184. };
  185. /**
  186. * Update glare on resize
  187. */
  188. const updateGlareSize = function () {
  189. this.glareElement.css({
  190. 'width': `${$(this).outerWidth()*2}`,
  191. 'height': `${$(this).outerWidth()*2}`,
  192. });
  193. };
  194. /**
  195. * Public methods
  196. */
  197. $.fn.tilt.destroy = function() {
  198. $(this).each(function () {
  199. $(this).find('.js-tilt-glare').remove();
  200. $(this).css({'will-change': '', 'transform': ''});
  201. $(this).off('mousemove mouseenter mouseleave');
  202. });
  203. };
  204. $.fn.tilt.getValues = function() {
  205. const results = [];
  206. $(this).each(function () {
  207. this.mousePositions = getMousePositions.call(this);
  208. results.push(getValues.call(this));
  209. });
  210. return results;
  211. };
  212. $.fn.tilt.reset = function() {
  213. $(this).each(function () {
  214. this.mousePositions = getMousePositions.call(this);
  215. this.settings = $(this).data('settings');
  216. mouseLeave.call(this);
  217. setTimeout(() => {
  218. this.reset = false;
  219. }, this.settings.transition);
  220. });
  221. };
  222. /**
  223. * Loop every instance
  224. */
  225. return this.each(function () {
  226. /**
  227. * Default settings merged with user settings
  228. * Can be set trough data attributes or as parameter.
  229. * @type {*}
  230. */
  231. this.settings = $.extend({
  232. maxTilt: $(this).is('[data-tilt-max]') ? $(this).data('tilt-max') : 8,
  233. perspective: $(this).is('[data-tilt-perspective]') ? $(this).data('tilt-perspective') : 300,
  234. easing: $(this).is('[data-tilt-easing]') ? $(this).data('tilt-easing') : 'cubic-bezier(.03,.98,.52,.99)',
  235. scale: $(this).is('[data-tilt-scale]') ? $(this).data('tilt-scale') : '1',
  236. speed: $(this).is('[data-tilt-speed]') ? $(this).data('tilt-speed') : '400',
  237. transition: $(this).is('[data-tilt-transition]') ? $(this).data('tilt-transition') : true,
  238. disableAxis: $(this).is('[data-tilt-disable-axis]') ? $(this).data('tilt-disable-axis') : null,
  239. axis: $(this).is('[data-tilt-axis]') ? $(this).data('tilt-axis') : null,
  240. reset: $(this).is('[data-tilt-reset]') ? $(this).data('tilt-reset') : true,
  241. glare: $(this).is('[data-tilt-glare]') ? $(this).data('tilt-glare') : false,
  242. maxGlare: $(this).is('[data-tilt-maxglare]') ? $(this).data('tilt-maxglare') : 1,
  243. }, options);
  244. // Add deprecation warning & set disableAxis to deprecated axis setting
  245. if(this.settings.axis !== null){
  246. console.warn('Tilt.js: the axis setting has been renamed to disableAxis. See https://github.com/gijsroge/tilt.js/pull/26 for more information');
  247. this.settings.disableAxis = this.settings.axis;
  248. }
  249. this.init = () => {
  250. // Store settings
  251. $(this).data('settings', this.settings);
  252. // Prepare element
  253. if(this.settings.glare) prepareGlare.call(this);
  254. // Bind events
  255. bindEvents.call(this);
  256. };
  257. // Init
  258. this.init();
  259. });
  260. };
  261. /**
  262. * Auto load
  263. */
  264. $('[data-tilt]').tilt();
  265. return true;
  266. }));