UNPKG

atropos

Version:

Touch-friendly 3D parallax hover effects

483 lines (476 loc) 16.4 kB
/** * Atropos 2.0.2 * Touch-friendly 3D parallax hover effects * https://atroposjs.com * * Copyright 2021-2023 * * Released under the MIT License * * Released on: July 4, 2023 */ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /* eslint-disable no-restricted-globals */ var $ = function $(el, sel) { return el.querySelector(sel); }; var $$ = function $$(el, sel) { return el.querySelectorAll(sel); }; var removeUndefinedProps = function removeUndefinedProps(obj) { if (obj === void 0) { obj = {}; } var result = {}; Object.keys(obj).forEach(function (key) { if (typeof obj[key] !== 'undefined') result[key] = obj[key]; }); return result; }; var defaults = { alwaysActive: false, activeOffset: 50, shadowOffset: 50, shadowScale: 1, duration: 300, rotate: true, rotateTouch: true, rotateXMax: 15, rotateYMax: 15, rotateXInvert: false, rotateYInvert: false, stretchX: 0, stretchY: 0, stretchZ: 0, commonOrigin: true, shadow: true, highlight: true }; function Atropos(originalParams) { if (originalParams === void 0) { originalParams = {}; } var _originalParams = originalParams, el = _originalParams.el, eventsEl = _originalParams.eventsEl; var _originalParams2 = originalParams, isComponent = _originalParams2.isComponent; var childrenRootEl; var self = { __atropos__: true, params: _extends({}, defaults, { onEnter: null, onLeave: null, onRotate: null }, removeUndefinedProps(originalParams || {})), destroyed: false, isActive: false }; var params = self.params; var rotateEl; var scaleEl; var innerEl; var elBoundingClientRect; var eventsElBoundingClientRect; var shadowEl; var highlightEl; var isScrolling; var clientXStart; var clientYStart; var queue = []; var queueFrameId; var purgeQueue = function purgeQueue() { queueFrameId = requestAnimationFrame(function () { queue.forEach(function (data) { if (typeof data === 'function') { data(); } else { var element = data.element, prop = data.prop, value = data.value; element.style[prop] = value; } }); queue.splice(0, queue.length); purgeQueue(); }); }; purgeQueue(); var $setDuration = function $setDuration(element, value) { queue.push({ element: element, prop: 'transitionDuration', value: value }); }; var $setEasing = function $setEasing(element, value) { queue.push({ element: element, prop: 'transitionTimingFunction', value: value }); }; var $setTransform = function $setTransform(element, value) { queue.push({ element: element, prop: 'transform', value: value }); }; var $setOpacity = function $setOpacity(element, value) { queue.push({ element: element, prop: 'opacity', value: value }); }; var $setOrigin = function $setOrigin(element, value) { queue.push({ element: element, prop: 'transformOrigin', value: value }); }; var $on = function $on(element, event, handler, props) { return element.addEventListener(event, handler, props); }; var $off = function $off(element, event, handler, props) { return element.removeEventListener(event, handler, props); }; var createShadow = function createShadow() { var created; shadowEl = $(el, '.atropos-shadow'); if (!shadowEl) { shadowEl = document.createElement('span'); shadowEl.classList.add('atropos-shadow'); created = true; } $setTransform(shadowEl, "translate3d(0,0,-" + params.shadowOffset + "px) scale(" + params.shadowScale + ")"); if (created) { rotateEl.appendChild(shadowEl); } }; var createHighlight = function createHighlight() { var created; highlightEl = $(el, '.atropos-highlight'); if (!highlightEl) { highlightEl = document.createElement('span'); highlightEl.classList.add('atropos-highlight'); created = true; } $setTransform(highlightEl, "translate3d(0,0,0)"); if (created) { innerEl.appendChild(highlightEl); } }; var setChildrenOffset = function setChildrenOffset(_ref) { var _ref$rotateXPercentag = _ref.rotateXPercentage, rotateXPercentage = _ref$rotateXPercentag === void 0 ? 0 : _ref$rotateXPercentag, _ref$rotateYPercentag = _ref.rotateYPercentage, rotateYPercentage = _ref$rotateYPercentag === void 0 ? 0 : _ref$rotateYPercentag, duration = _ref.duration, opacityOnly = _ref.opacityOnly, easeOut = _ref.easeOut; var getOpacity = function getOpacity(element) { if (element.dataset.atroposOpacity && typeof element.dataset.atroposOpacity === 'string') { return element.dataset.atroposOpacity.split(';').map(function (v) { return parseFloat(v); }); } return undefined; }; $$(childrenRootEl, '[data-atropos-offset], [data-atropos-opacity]').forEach(function (childEl) { $setDuration(childEl, duration); $setEasing(childEl, easeOut ? 'ease-out' : ''); var elementOpacity = getOpacity(childEl); if (rotateXPercentage === 0 && rotateYPercentage === 0) { if (!opacityOnly) $setTransform(childEl, "translate3d(0, 0, 0)"); if (elementOpacity) $setOpacity(childEl, elementOpacity[0]); } else { var childElOffset = parseFloat(childEl.dataset.atroposOffset) / 100; if (!Number.isNaN(childElOffset) && !opacityOnly) { $setTransform(childEl, "translate3d(" + -rotateYPercentage * -childElOffset + "%, " + rotateXPercentage * -childElOffset + "%, 0)"); } if (elementOpacity) { var min = elementOpacity[0], max = elementOpacity[1]; var rotatePercentage = Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)); $setOpacity(childEl, min + (max - min) * rotatePercentage / 100); } } }); }; var setElements = function setElements(clientX, clientY) { var isMultiple = el !== eventsEl; if (!elBoundingClientRect) { elBoundingClientRect = el.getBoundingClientRect(); } if (isMultiple && !eventsElBoundingClientRect) { eventsElBoundingClientRect = eventsEl.getBoundingClientRect(); } if (typeof clientX === 'undefined' && typeof clientY === 'undefined') { var rect = isMultiple ? eventsElBoundingClientRect : elBoundingClientRect; clientX = rect.left + rect.width / 2; clientY = rect.top + rect.height / 2; } var rotateX = 0; var rotateY = 0; var _elBoundingClientRect = elBoundingClientRect, top = _elBoundingClientRect.top, left = _elBoundingClientRect.left, width = _elBoundingClientRect.width, height = _elBoundingClientRect.height; var transformOrigin; if (!isMultiple) { var centerX = width / 2; var centerY = height / 2; var coordX = clientX - left; var coordY = clientY - top; rotateY = params.rotateYMax * (coordX - centerX) / (width / 2) * -1; rotateX = params.rotateXMax * (coordY - centerY) / (height / 2); } else { var _eventsElBoundingClie = eventsElBoundingClientRect, parentTop = _eventsElBoundingClie.top, parentLeft = _eventsElBoundingClie.left, parentWidth = _eventsElBoundingClie.width, parentHeight = _eventsElBoundingClie.height; var offsetLeft = left - parentLeft; var offsetTop = top - parentTop; var _centerX = width / 2 + offsetLeft; var _centerY = height / 2 + offsetTop; var _coordX = clientX - parentLeft; var _coordY = clientY - parentTop; rotateY = params.rotateYMax * (_coordX - _centerX) / (parentWidth - width / 2) * -1; rotateX = params.rotateXMax * (_coordY - _centerY) / (parentHeight - height / 2); transformOrigin = clientX - left + "px " + (clientY - top) + "px"; } rotateX = Math.min(Math.max(-rotateX, -params.rotateXMax), params.rotateXMax); if (params.rotateXInvert) rotateX = -rotateX; rotateY = Math.min(Math.max(-rotateY, -params.rotateYMax), params.rotateYMax); if (params.rotateYInvert) rotateY = -rotateY; var rotateXPercentage = rotateX / params.rotateXMax * 100; var rotateYPercentage = rotateY / params.rotateYMax * 100; var stretchX = (isMultiple ? rotateYPercentage / 100 * params.stretchX : 0) * (params.rotateYInvert ? -1 : 1); var stretchY = (isMultiple ? rotateXPercentage / 100 * params.stretchY : 0) * (params.rotateXInvert ? -1 : 1); var stretchZ = isMultiple ? Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100 * params.stretchZ : 0; $setTransform(rotateEl, "translate3d(" + stretchX + "%, " + -stretchY + "%, " + -stretchZ + "px) rotateX(" + rotateX + "deg) rotateY(" + rotateY + "deg)"); if (transformOrigin && params.commonOrigin) { $setOrigin(rotateEl, transformOrigin); } if (highlightEl) { $setDuration(highlightEl, params.duration + "ms"); $setEasing(highlightEl, 'ease-out'); $setTransform(highlightEl, "translate3d(" + -rotateYPercentage * 0.25 + "%, " + rotateXPercentage * 0.25 + "%, 0)"); $setOpacity(highlightEl, Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100); } setChildrenOffset({ rotateXPercentage: rotateXPercentage, rotateYPercentage: rotateYPercentage, duration: params.duration + "ms", easeOut: true }); if (typeof params.onRotate === 'function') params.onRotate(rotateX, rotateY); }; var activate = function activate() { queue.push(function () { return el.classList.add('atropos-active'); }); $setDuration(rotateEl, params.duration + "ms"); $setEasing(rotateEl, 'ease-out'); $setTransform(scaleEl, "translate3d(0,0, " + params.activeOffset + "px)"); $setDuration(scaleEl, params.duration + "ms"); $setEasing(scaleEl, 'ease-out'); if (shadowEl) { $setDuration(shadowEl, params.duration + "ms"); $setEasing(shadowEl, 'ease-out'); } self.isActive = true; }; var onPointerEnter = function onPointerEnter(e) { isScrolling = undefined; if (e.type === 'pointerdown' && e.pointerType === 'mouse') return; if (e.type === 'pointerenter' && e.pointerType !== 'mouse') return; if (e.type === 'pointerdown') { e.preventDefault(); } clientXStart = e.clientX; clientYStart = e.clientY; if (params.alwaysActive) { elBoundingClientRect = undefined; eventsElBoundingClientRect = undefined; return; } activate(); if (typeof params.onEnter === 'function') params.onEnter(); }; var onTouchMove = function onTouchMove(e) { if (isScrolling === false && e.cancelable) { e.preventDefault(); } }; var onPointerMove = function onPointerMove(e) { if (!params.rotate || !self.isActive) return; if (e.pointerType !== 'mouse') { if (!params.rotateTouch) return; e.preventDefault(); } var clientX = e.clientX, clientY = e.clientY; var diffX = clientX - clientXStart; var diffY = clientY - clientYStart; if (typeof params.rotateTouch === 'string' && (diffX !== 0 || diffY !== 0) && typeof isScrolling === 'undefined') { if (diffX * diffX + diffY * diffY >= 25) { var touchAngle = Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180 / Math.PI; isScrolling = params.rotateTouch === 'scroll-y' ? touchAngle > 45 : 90 - touchAngle > 45; } if (isScrolling === false) { el.classList.add('atropos-rotate-touch'); if (e.cancelable) { e.preventDefault(); } } } if (e.pointerType !== 'mouse' && isScrolling) { return; } setElements(clientX, clientY); }; var onPointerLeave = function onPointerLeave(e) { elBoundingClientRect = undefined; eventsElBoundingClientRect = undefined; if (!self.isActive) return; if (e && e.type === 'pointerup' && e.pointerType === 'mouse') return; if (e && e.type === 'pointerleave' && e.pointerType !== 'mouse') return; if (typeof params.rotateTouch === 'string' && isScrolling) { el.classList.remove('atropos-rotate-touch'); } if (params.alwaysActive) { setElements(); if (typeof params.onRotate === 'function') params.onRotate(0, 0); if (typeof params.onLeave === 'function') params.onLeave(); return; } queue.push(function () { return el.classList.remove('atropos-active'); }); $setDuration(scaleEl, params.duration + "ms"); $setEasing(scaleEl, ''); $setTransform(scaleEl, "translate3d(0,0, " + 0 + "px)"); if (shadowEl) { $setDuration(shadowEl, params.duration + "ms"); $setEasing(shadowEl, ''); } if (highlightEl) { $setDuration(highlightEl, params.duration + "ms"); $setEasing(highlightEl, ''); $setTransform(highlightEl, "translate3d(0, 0, 0)"); $setOpacity(highlightEl, 0); } $setDuration(rotateEl, params.duration + "ms"); $setEasing(rotateEl, ''); $setTransform(rotateEl, "translate3d(0,0,0) rotateX(0deg) rotateY(0deg)"); setChildrenOffset({ duration: params.duration + "ms" }); self.isActive = false; if (typeof params.onRotate === 'function') params.onRotate(0, 0); if (typeof params.onLeave === 'function') params.onLeave(); }; var onDocumentClick = function onDocumentClick(e) { var clickTarget = e.target; if (!eventsEl.contains(clickTarget) && clickTarget !== eventsEl && self.isActive) { onPointerLeave(); } }; var initDOM = function initDOM() { if (typeof el === 'string') { el = $(document, el); } if (!el) return; // eslint-disable-next-line if (el.__atropos__) return; if (typeof eventsEl !== 'undefined') { if (typeof eventsEl === 'string') { eventsEl = $(document, eventsEl); } } else { eventsEl = el; } childrenRootEl = isComponent ? el.parentNode.host : el; Object.assign(self, { el: el }); rotateEl = $(el, '.atropos-rotate'); scaleEl = $(el, '.atropos-scale'); innerEl = $(el, '.atropos-inner'); // eslint-disable-next-line el.__atropos__ = self; }; var init = function init() { initDOM(); if (!el || !eventsEl) return; if (params.shadow) { createShadow(); } if (params.highlight) { createHighlight(); } if (params.rotateTouch) { if (typeof params.rotateTouch === 'string') { el.classList.add("atropos-rotate-touch-" + params.rotateTouch); } else { el.classList.add('atropos-rotate-touch'); } } if ($(childrenRootEl, '[data-atropos-opacity]')) { setChildrenOffset({ opacityOnly: true }); } $on(document, 'click', onDocumentClick); $on(eventsEl, 'pointerdown', onPointerEnter); $on(eventsEl, 'pointerenter', onPointerEnter); $on(eventsEl, 'pointermove', onPointerMove); $on(eventsEl, 'touchmove', onTouchMove); $on(eventsEl, 'pointerleave', onPointerLeave); $on(eventsEl, 'pointerup', onPointerLeave); $on(eventsEl, 'lostpointercapture', onPointerLeave); if (params.alwaysActive) { activate(); setElements(); } }; var destroy = function destroy() { self.destroyed = true; cancelAnimationFrame(queueFrameId); $off(document, 'click', onDocumentClick); $off(eventsEl, 'pointerdown', onPointerEnter); $off(eventsEl, 'pointerenter', onPointerEnter); $off(eventsEl, 'pointermove', onPointerMove); $off(eventsEl, 'touchmove', onTouchMove); $off(eventsEl, 'pointerleave', onPointerLeave); $off(eventsEl, 'pointerup', onPointerLeave); $off(eventsEl, 'lostpointercapture', onPointerLeave); // eslint-disable-next-line delete el.__atropos__; }; self.destroy = destroy; init(); // eslint-disable-next-line return self; } export { Atropos, Atropos as default, defaults }; //# sourceMappingURL=atropos.mjs.map