atropos.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. /*!
  2. Atropos
  3. Version: 2.0.2
  4. Plugin URL: https://atroposjs.com
  5. License: Copyright 2021-2024 | Released under the MIT License
  6. !*/
  7. var Atropos = (function () {
  8. 'use strict';
  9. function _extends() {
  10. _extends = Object.assign ? Object.assign.bind() : function (target) {
  11. for (var i = 1; i < arguments.length; i++) {
  12. var source = arguments[i];
  13. for (var key in source) {
  14. if (Object.prototype.hasOwnProperty.call(source, key)) {
  15. target[key] = source[key];
  16. }
  17. }
  18. }
  19. return target;
  20. };
  21. return _extends.apply(this, arguments);
  22. }
  23. /* eslint-disable no-restricted-globals */
  24. var $ = function $(el, sel) {
  25. return el.querySelector(sel);
  26. };
  27. var $$ = function $$(el, sel) {
  28. return el.querySelectorAll(sel);
  29. };
  30. var removeUndefinedProps = function removeUndefinedProps(obj) {
  31. if (obj === void 0) {
  32. obj = {};
  33. }
  34. var result = {};
  35. Object.keys(obj).forEach(function (key) {
  36. if (typeof obj[key] !== 'undefined') result[key] = obj[key];
  37. });
  38. return result;
  39. };
  40. var defaults = {
  41. alwaysActive: false,
  42. activeOffset: 50,
  43. shadowOffset: 50,
  44. shadowScale: 1,
  45. duration: 300,
  46. rotate: true,
  47. rotateTouch: true,
  48. rotateXMax: 15,
  49. rotateYMax: 15,
  50. rotateXInvert: false,
  51. rotateYInvert: false,
  52. stretchX: 0,
  53. stretchY: 0,
  54. stretchZ: 0,
  55. commonOrigin: true,
  56. shadow: true,
  57. highlight: true
  58. };
  59. function Atropos(originalParams) {
  60. if (originalParams === void 0) {
  61. originalParams = {};
  62. }
  63. var _originalParams = originalParams,
  64. el = _originalParams.el,
  65. eventsEl = _originalParams.eventsEl;
  66. var _originalParams2 = originalParams,
  67. isComponent = _originalParams2.isComponent;
  68. var childrenRootEl;
  69. var self = {
  70. __atropos__: true,
  71. params: _extends({}, defaults, {
  72. onEnter: null,
  73. onLeave: null,
  74. onRotate: null
  75. }, removeUndefinedProps(originalParams || {})),
  76. destroyed: false,
  77. isActive: false
  78. };
  79. var params = self.params;
  80. var rotateEl;
  81. var scaleEl;
  82. var innerEl;
  83. var elBoundingClientRect;
  84. var eventsElBoundingClientRect;
  85. var shadowEl;
  86. var highlightEl;
  87. var isScrolling;
  88. var clientXStart;
  89. var clientYStart;
  90. var queue = [];
  91. var queueFrameId;
  92. var purgeQueue = function purgeQueue() {
  93. queueFrameId = requestAnimationFrame(function () {
  94. queue.forEach(function (data) {
  95. if (typeof data === 'function') {
  96. data();
  97. } else {
  98. var element = data.element,
  99. prop = data.prop,
  100. value = data.value;
  101. element.style[prop] = value;
  102. }
  103. });
  104. queue.splice(0, queue.length);
  105. purgeQueue();
  106. });
  107. };
  108. purgeQueue();
  109. var $setDuration = function $setDuration(element, value) {
  110. queue.push({
  111. element: element,
  112. prop: 'transitionDuration',
  113. value: value
  114. });
  115. };
  116. var $setEasing = function $setEasing(element, value) {
  117. queue.push({
  118. element: element,
  119. prop: 'transitionTimingFunction',
  120. value: value
  121. });
  122. };
  123. var $setTransform = function $setTransform(element, value) {
  124. queue.push({
  125. element: element,
  126. prop: 'transform',
  127. value: value
  128. });
  129. };
  130. var $setOpacity = function $setOpacity(element, value) {
  131. queue.push({
  132. element: element,
  133. prop: 'opacity',
  134. value: value
  135. });
  136. };
  137. var $setOrigin = function $setOrigin(element, value) {
  138. queue.push({
  139. element: element,
  140. prop: 'transformOrigin',
  141. value: value
  142. });
  143. };
  144. var $on = function $on(element, event, handler, props) {
  145. return element.addEventListener(event, handler, props);
  146. };
  147. var $off = function $off(element, event, handler, props) {
  148. return element.removeEventListener(event, handler, props);
  149. };
  150. var createShadow = function createShadow() {
  151. var created;
  152. shadowEl = $(el, '.atropos-shadow');
  153. if (!shadowEl) {
  154. shadowEl = document.createElement('span');
  155. shadowEl.classList.add('atropos-shadow');
  156. created = true;
  157. }
  158. $setTransform(shadowEl, "translate3d(0,0,-" + params.shadowOffset + "px) scale(" + params.shadowScale + ")");
  159. if (created) {
  160. rotateEl.appendChild(shadowEl);
  161. }
  162. };
  163. var createHighlight = function createHighlight() {
  164. var created;
  165. highlightEl = $(el, '.atropos-highlight');
  166. if (!highlightEl) {
  167. highlightEl = document.createElement('span');
  168. highlightEl.classList.add('atropos-highlight');
  169. created = true;
  170. }
  171. $setTransform(highlightEl, "translate3d(0,0,0)");
  172. if (created) {
  173. innerEl.appendChild(highlightEl);
  174. }
  175. };
  176. var setChildrenOffset = function setChildrenOffset(_ref) {
  177. var _ref$rotateXPercentag = _ref.rotateXPercentage,
  178. rotateXPercentage = _ref$rotateXPercentag === void 0 ? 0 : _ref$rotateXPercentag,
  179. _ref$rotateYPercentag = _ref.rotateYPercentage,
  180. rotateYPercentage = _ref$rotateYPercentag === void 0 ? 0 : _ref$rotateYPercentag,
  181. duration = _ref.duration,
  182. opacityOnly = _ref.opacityOnly,
  183. easeOut = _ref.easeOut;
  184. var getOpacity = function getOpacity(element) {
  185. if (element.dataset.atroposOpacity && typeof element.dataset.atroposOpacity === 'string') {
  186. return element.dataset.atroposOpacity.split(';').map(function (v) {
  187. return parseFloat(v);
  188. });
  189. }
  190. return undefined;
  191. };
  192. $$(childrenRootEl, '[data-atropos-offset], [data-atropos-opacity]').forEach(function (childEl) {
  193. $setDuration(childEl, duration);
  194. $setEasing(childEl, easeOut ? 'ease-out' : '');
  195. var elementOpacity = getOpacity(childEl);
  196. if (rotateXPercentage === 0 && rotateYPercentage === 0) {
  197. if (!opacityOnly) $setTransform(childEl, "translate3d(0, 0, 0)");
  198. if (elementOpacity) $setOpacity(childEl, elementOpacity[0]);
  199. } else {
  200. var childElOffset = parseFloat(childEl.dataset.atroposOffset) / 100;
  201. if (!Number.isNaN(childElOffset) && !opacityOnly) {
  202. $setTransform(childEl, "translate3d(" + -rotateYPercentage * -childElOffset + "%, " + rotateXPercentage * -childElOffset + "%, 0)");
  203. }
  204. if (elementOpacity) {
  205. var min = elementOpacity[0],
  206. max = elementOpacity[1];
  207. var rotatePercentage = Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage));
  208. $setOpacity(childEl, min + (max - min) * rotatePercentage / 100);
  209. }
  210. }
  211. });
  212. };
  213. var setElements = function setElements(clientX, clientY) {
  214. var isMultiple = el !== eventsEl;
  215. if (!elBoundingClientRect) {
  216. elBoundingClientRect = el.getBoundingClientRect();
  217. }
  218. if (isMultiple && !eventsElBoundingClientRect) {
  219. eventsElBoundingClientRect = eventsEl.getBoundingClientRect();
  220. }
  221. if (typeof clientX === 'undefined' && typeof clientY === 'undefined') {
  222. var rect = isMultiple ? eventsElBoundingClientRect : elBoundingClientRect;
  223. clientX = rect.left + rect.width / 2;
  224. clientY = rect.top + rect.height / 2;
  225. }
  226. var rotateX = 0;
  227. var rotateY = 0;
  228. var _elBoundingClientRect = elBoundingClientRect,
  229. top = _elBoundingClientRect.top,
  230. left = _elBoundingClientRect.left,
  231. width = _elBoundingClientRect.width,
  232. height = _elBoundingClientRect.height;
  233. var transformOrigin;
  234. if (!isMultiple) {
  235. var centerX = width / 2;
  236. var centerY = height / 2;
  237. var coordX = clientX - left;
  238. var coordY = clientY - top;
  239. rotateY = params.rotateYMax * (coordX - centerX) / (width / 2) * -1;
  240. rotateX = params.rotateXMax * (coordY - centerY) / (height / 2);
  241. } else {
  242. var _eventsElBoundingClie = eventsElBoundingClientRect,
  243. parentTop = _eventsElBoundingClie.top,
  244. parentLeft = _eventsElBoundingClie.left,
  245. parentWidth = _eventsElBoundingClie.width,
  246. parentHeight = _eventsElBoundingClie.height;
  247. var offsetLeft = left - parentLeft;
  248. var offsetTop = top - parentTop;
  249. var _centerX = width / 2 + offsetLeft;
  250. var _centerY = height / 2 + offsetTop;
  251. var _coordX = clientX - parentLeft;
  252. var _coordY = clientY - parentTop;
  253. rotateY = params.rotateYMax * (_coordX - _centerX) / (parentWidth - width / 2) * -1;
  254. rotateX = params.rotateXMax * (_coordY - _centerY) / (parentHeight - height / 2);
  255. transformOrigin = clientX - left + "px " + (clientY - top) + "px";
  256. }
  257. rotateX = Math.min(Math.max(-rotateX, -params.rotateXMax), params.rotateXMax);
  258. if (params.rotateXInvert) rotateX = -rotateX;
  259. rotateY = Math.min(Math.max(-rotateY, -params.rotateYMax), params.rotateYMax);
  260. if (params.rotateYInvert) rotateY = -rotateY;
  261. var rotateXPercentage = rotateX / params.rotateXMax * 100;
  262. var rotateYPercentage = rotateY / params.rotateYMax * 100;
  263. var stretchX = (isMultiple ? rotateYPercentage / 100 * params.stretchX : 0) * (params.rotateYInvert ? -1 : 1);
  264. var stretchY = (isMultiple ? rotateXPercentage / 100 * params.stretchY : 0) * (params.rotateXInvert ? -1 : 1);
  265. var stretchZ = isMultiple ? Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100 * params.stretchZ : 0;
  266. $setTransform(rotateEl, "translate3d(" + stretchX + "%, " + -stretchY + "%, " + -stretchZ + "px) rotateX(" + rotateX + "deg) rotateY(" + rotateY + "deg)");
  267. if (transformOrigin && params.commonOrigin) {
  268. $setOrigin(rotateEl, transformOrigin);
  269. }
  270. if (highlightEl) {
  271. $setDuration(highlightEl, params.duration + "ms");
  272. $setEasing(highlightEl, 'ease-out');
  273. $setTransform(highlightEl, "translate3d(" + -rotateYPercentage * 0.25 + "%, " + rotateXPercentage * 0.25 + "%, 0)");
  274. $setOpacity(highlightEl, Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100);
  275. }
  276. setChildrenOffset({
  277. rotateXPercentage: rotateXPercentage,
  278. rotateYPercentage: rotateYPercentage,
  279. duration: params.duration + "ms",
  280. easeOut: true
  281. });
  282. if (typeof params.onRotate === 'function') params.onRotate(rotateX, rotateY);
  283. };
  284. var activate = function activate() {
  285. queue.push(function () {
  286. return el.classList.add('atropos-active');
  287. });
  288. $setDuration(rotateEl, params.duration + "ms");
  289. $setEasing(rotateEl, 'ease-out');
  290. $setTransform(scaleEl, "translate3d(0,0, " + params.activeOffset + "px)");
  291. $setDuration(scaleEl, params.duration + "ms");
  292. $setEasing(scaleEl, 'ease-out');
  293. if (shadowEl) {
  294. $setDuration(shadowEl, params.duration + "ms");
  295. $setEasing(shadowEl, 'ease-out');
  296. }
  297. self.isActive = true;
  298. };
  299. var onPointerEnter = function onPointerEnter(e) {
  300. isScrolling = undefined;
  301. if (e.type === 'pointerdown' && e.pointerType === 'mouse') return;
  302. if (e.type === 'pointerenter' && e.pointerType !== 'mouse') return;
  303. if (e.type === 'pointerdown') {
  304. e.preventDefault();
  305. }
  306. clientXStart = e.clientX;
  307. clientYStart = e.clientY;
  308. if (params.alwaysActive) {
  309. elBoundingClientRect = undefined;
  310. eventsElBoundingClientRect = undefined;
  311. return;
  312. }
  313. activate();
  314. if (typeof params.onEnter === 'function') params.onEnter();
  315. };
  316. var onTouchMove = function onTouchMove(e) {
  317. if (isScrolling === false && e.cancelable) {
  318. e.preventDefault();
  319. }
  320. };
  321. var onPointerMove = function onPointerMove(e) {
  322. if (!params.rotate || !self.isActive) return;
  323. if (e.pointerType !== 'mouse') {
  324. if (!params.rotateTouch) return;
  325. e.preventDefault();
  326. }
  327. var clientX = e.clientX,
  328. clientY = e.clientY;
  329. var diffX = clientX - clientXStart;
  330. var diffY = clientY - clientYStart;
  331. if (typeof params.rotateTouch === 'string' && (diffX !== 0 || diffY !== 0) && typeof isScrolling === 'undefined') {
  332. if (diffX * diffX + diffY * diffY >= 25) {
  333. var touchAngle = Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180 / Math.PI;
  334. isScrolling = params.rotateTouch === 'scroll-y' ? touchAngle > 45 : 90 - touchAngle > 45;
  335. }
  336. if (isScrolling === false) {
  337. el.classList.add('atropos-rotate-touch');
  338. if (e.cancelable) {
  339. e.preventDefault();
  340. }
  341. }
  342. }
  343. if (e.pointerType !== 'mouse' && isScrolling) {
  344. return;
  345. }
  346. setElements(clientX, clientY);
  347. };
  348. var onPointerLeave = function onPointerLeave(e) {
  349. elBoundingClientRect = undefined;
  350. eventsElBoundingClientRect = undefined;
  351. if (!self.isActive) return;
  352. if (e && e.type === 'pointerup' && e.pointerType === 'mouse') return;
  353. if (e && e.type === 'pointerleave' && e.pointerType !== 'mouse') return;
  354. if (typeof params.rotateTouch === 'string' && isScrolling) {
  355. el.classList.remove('atropos-rotate-touch');
  356. }
  357. if (params.alwaysActive) {
  358. setElements();
  359. if (typeof params.onRotate === 'function') params.onRotate(0, 0);
  360. if (typeof params.onLeave === 'function') params.onLeave();
  361. return;
  362. }
  363. queue.push(function () {
  364. return el.classList.remove('atropos-active');
  365. });
  366. $setDuration(scaleEl, params.duration + "ms");
  367. $setEasing(scaleEl, '');
  368. $setTransform(scaleEl, "translate3d(0,0, " + 0 + "px)");
  369. if (shadowEl) {
  370. $setDuration(shadowEl, params.duration + "ms");
  371. $setEasing(shadowEl, '');
  372. }
  373. if (highlightEl) {
  374. $setDuration(highlightEl, params.duration + "ms");
  375. $setEasing(highlightEl, '');
  376. $setTransform(highlightEl, "translate3d(0, 0, 0)");
  377. $setOpacity(highlightEl, 0);
  378. }
  379. $setDuration(rotateEl, params.duration + "ms");
  380. $setEasing(rotateEl, '');
  381. $setTransform(rotateEl, "translate3d(0,0,0) rotateX(0deg) rotateY(0deg)");
  382. setChildrenOffset({
  383. duration: params.duration + "ms"
  384. });
  385. self.isActive = false;
  386. if (typeof params.onRotate === 'function') params.onRotate(0, 0);
  387. if (typeof params.onLeave === 'function') params.onLeave();
  388. };
  389. var onDocumentClick = function onDocumentClick(e) {
  390. var clickTarget = e.target;
  391. if (!eventsEl.contains(clickTarget) && clickTarget !== eventsEl && self.isActive) {
  392. onPointerLeave();
  393. }
  394. };
  395. var initDOM = function initDOM() {
  396. if (typeof el === 'string') {
  397. el = $(document, el);
  398. }
  399. if (!el) return;
  400. // eslint-disable-next-line
  401. if (el.__atropos__) return;
  402. if (typeof eventsEl !== 'undefined') {
  403. if (typeof eventsEl === 'string') {
  404. eventsEl = $(document, eventsEl);
  405. }
  406. } else {
  407. eventsEl = el;
  408. }
  409. childrenRootEl = isComponent ? el.parentNode.host : el;
  410. Object.assign(self, {
  411. el: el
  412. });
  413. rotateEl = $(el, '.atropos-rotate');
  414. scaleEl = $(el, '.atropos-scale');
  415. innerEl = $(el, '.atropos-inner');
  416. // eslint-disable-next-line
  417. el.__atropos__ = self;
  418. };
  419. var init = function init() {
  420. initDOM();
  421. if (!el || !eventsEl) return;
  422. if (params.shadow) {
  423. createShadow();
  424. }
  425. if (params.highlight) {
  426. createHighlight();
  427. }
  428. if (params.rotateTouch) {
  429. if (typeof params.rotateTouch === 'string') {
  430. el.classList.add("atropos-rotate-touch-" + params.rotateTouch);
  431. } else {
  432. el.classList.add('atropos-rotate-touch');
  433. }
  434. }
  435. if ($(childrenRootEl, '[data-atropos-opacity]')) {
  436. setChildrenOffset({
  437. opacityOnly: true
  438. });
  439. }
  440. $on(document, 'click', onDocumentClick);
  441. $on(eventsEl, 'pointerdown', onPointerEnter);
  442. $on(eventsEl, 'pointerenter', onPointerEnter);
  443. $on(eventsEl, 'pointermove', onPointerMove);
  444. $on(eventsEl, 'touchmove', onTouchMove);
  445. $on(eventsEl, 'pointerleave', onPointerLeave);
  446. $on(eventsEl, 'pointerup', onPointerLeave);
  447. $on(eventsEl, 'lostpointercapture', onPointerLeave);
  448. if (params.alwaysActive) {
  449. activate();
  450. setElements();
  451. }
  452. };
  453. var destroy = function destroy() {
  454. self.destroyed = true;
  455. cancelAnimationFrame(queueFrameId);
  456. $off(document, 'click', onDocumentClick);
  457. $off(eventsEl, 'pointerdown', onPointerEnter);
  458. $off(eventsEl, 'pointerenter', onPointerEnter);
  459. $off(eventsEl, 'pointermove', onPointerMove);
  460. $off(eventsEl, 'touchmove', onTouchMove);
  461. $off(eventsEl, 'pointerleave', onPointerLeave);
  462. $off(eventsEl, 'pointerup', onPointerLeave);
  463. $off(eventsEl, 'lostpointercapture', onPointerLeave);
  464. // eslint-disable-next-line
  465. delete el.__atropos__;
  466. };
  467. self.destroy = destroy;
  468. init();
  469. // eslint-disable-next-line
  470. return self;
  471. }
  472. return Atropos;
  473. })();