atropos
Version:
Touch-friendly 3D parallax hover effects
13 lines (12 loc) • 9.57 kB
JavaScript
/**
* 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
*/
const $=(t,e)=>t.querySelector(e),$$=(t,e)=>t.querySelectorAll(e),removeUndefinedProps=(t={})=>{const e={};return Object.keys(t).forEach((o=>{void 0!==t[o]&&(e[o]=t[o])})),e},defaults={alwaysActive:!1,activeOffset:50,shadowOffset:50,shadowScale:1,duration:300,rotate:!0,rotateTouch:!0,rotateXMax:15,rotateYMax:15,rotateXInvert:!1,rotateYInvert:!1,stretchX:0,stretchY:0,stretchZ:0,commonOrigin:!0,shadow:!0,highlight:!0};function Atropos(t={}){let{el:e,eventsEl:o}=t;const{isComponent:a}=t;let s;const r={__atropos__:!0,params:{...defaults,onEnter:null,onLeave:null,onRotate:null,...removeUndefinedProps(t||{})},destroyed:!1,isActive:!1},{params:n}=r;let i,p,c,l,d,h,u,f,m,v;const y=[];let g;const w=()=>{g=requestAnimationFrame((()=>{y.forEach((t=>{if("function"==typeof t)t();else{const{element:e,prop:o,value:a}=t;e.style[o]=a}})),y.splice(0,y.length),w()}))};w();const x=(t,e)=>{y.push({element:t,prop:"transitionDuration",value:e})},b=(t,e)=>{y.push({element:t,prop:"transitionTimingFunction",value:e})},M=(t,e)=>{y.push({element:t,prop:"transform",value:e})},E=(t,e)=>{y.push({element:t,prop:"opacity",value:e})},L=(t,e,o,a)=>t.addEventListener(e,o,a),C=(t,e,o,a)=>t.removeEventListener(e,o,a),A=({rotateXPercentage:t=0,rotateYPercentage:e=0,duration:o,opacityOnly:a,easeOut:r})=>{$$(s,"[data-atropos-offset], [data-atropos-opacity]").forEach((s=>{x(s,o),b(s,r?"ease-out":"");const n=(t=>{if(t.dataset.atroposOpacity&&"string"==typeof t.dataset.atroposOpacity)return t.dataset.atroposOpacity.split(";").map((t=>parseFloat(t)))})(s);if(0===t&&0===e)a||M(s,"translate3d(0, 0, 0)"),n&&E(s,n[0]);else{const o=parseFloat(s.dataset.atroposOffset)/100;if(Number.isNaN(o)||a||M(s,`translate3d(${-e*-o}%, ${t*-o}%, 0)`),n){const[o,a]=n,r=Math.max(Math.abs(t),Math.abs(e));E(s,o+(a-o)*r/100)}}}))},O=(t,a)=>{const s=e!==o;if(l||(l=e.getBoundingClientRect()),s&&!d&&(d=o.getBoundingClientRect()),void 0===t&&void 0===a){const e=s?d:l;t=e.left+e.width/2,a=e.top+e.height/2}let r=0,p=0;const{top:c,left:h,width:f,height:m}=l;let v;if(s){const{top:e,left:o,width:s,height:i}=d,l=f/2+(h-o),u=m/2+(c-e),y=t-o,g=a-e;p=n.rotateYMax*(y-l)/(s-f/2)*-1,r=n.rotateXMax*(g-u)/(i-m/2),v=`${t-h}px ${a-c}px`}else{const e=f/2,o=m/2,s=t-h,i=a-c;p=n.rotateYMax*(s-e)/(f/2)*-1,r=n.rotateXMax*(i-o)/(m/2)}r=Math.min(Math.max(-r,-n.rotateXMax),n.rotateXMax),n.rotateXInvert&&(r=-r),p=Math.min(Math.max(-p,-n.rotateYMax),n.rotateYMax),n.rotateYInvert&&(p=-p);const g=r/n.rotateXMax*100,w=p/n.rotateYMax*100,$=(s?w/100*n.stretchX:0)*(n.rotateYInvert?-1:1),L=(s?g/100*n.stretchY:0)*(n.rotateXInvert?-1:1),C=s?Math.max(Math.abs(g),Math.abs(w))/100*n.stretchZ:0;var O,S;M(i,`translate3d(${$}%, ${-L}%, ${-C}px) rotateX(${r}deg) rotateY(${p}deg)`),v&&n.commonOrigin&&(O=i,S=v,y.push({element:O,prop:"transformOrigin",value:S})),u&&(x(u,`${n.duration}ms`),b(u,"ease-out"),M(u,`translate3d(${.25*-w}%, ${.25*g}%, 0)`),E(u,Math.max(Math.abs(g),Math.abs(w))/100)),A({rotateXPercentage:g,rotateYPercentage:w,duration:`${n.duration}ms`,easeOut:!0}),"function"==typeof n.onRotate&&n.onRotate(r,p)},S=()=>{y.push((()=>e.classList.add("atropos-active"))),x(i,`${n.duration}ms`),b(i,"ease-out"),M(p,`translate3d(0,0, ${n.activeOffset}px)`),x(p,`${n.duration}ms`),b(p,"ease-out"),h&&(x(h,`${n.duration}ms`),b(h,"ease-out")),r.isActive=!0},T=t=>{if(f=void 0,!("pointerdown"===t.type&&"mouse"===t.pointerType||"pointerenter"===t.type&&"mouse"!==t.pointerType)){if("pointerdown"===t.type&&t.preventDefault(),m=t.clientX,v=t.clientY,n.alwaysActive)return l=void 0,void(d=void 0);S(),"function"==typeof n.onEnter&&n.onEnter()}},X=t=>{!1===f&&t.cancelable&&t.preventDefault()},Y=t=>{if(!n.rotate||!r.isActive)return;if("mouse"!==t.pointerType){if(!n.rotateTouch)return;t.preventDefault()}const{clientX:o,clientY:a}=t,s=o-m,i=a-v;if("string"==typeof n.rotateTouch&&(0!==s||0!==i)&&void 0===f){if(s*s+i*i>=25){const t=180*Math.atan2(Math.abs(i),Math.abs(s))/Math.PI;f="scroll-y"===n.rotateTouch?t>45:90-t>45}!1===f&&(e.classList.add("atropos-rotate-touch"),t.cancelable&&t.preventDefault())}"mouse"!==t.pointerType&&f||O(o,a)},k=t=>{if(l=void 0,d=void 0,r.isActive&&!(t&&"pointerup"===t.type&&"mouse"===t.pointerType||t&&"pointerleave"===t.type&&"mouse"!==t.pointerType)){if("string"==typeof n.rotateTouch&&f&&e.classList.remove("atropos-rotate-touch"),n.alwaysActive)return O(),"function"==typeof n.onRotate&&n.onRotate(0,0),void("function"==typeof n.onLeave&&n.onLeave());y.push((()=>e.classList.remove("atropos-active"))),x(p,`${n.duration}ms`),b(p,""),M(p,"translate3d(0,0, 0px)"),h&&(x(h,`${n.duration}ms`),b(h,"")),u&&(x(u,`${n.duration}ms`),b(u,""),M(u,"translate3d(0, 0, 0)"),E(u,0)),x(i,`${n.duration}ms`),b(i,""),M(i,"translate3d(0,0,0) rotateX(0deg) rotateY(0deg)"),A({duration:`${n.duration}ms`}),r.isActive=!1,"function"==typeof n.onRotate&&n.onRotate(0,0),"function"==typeof n.onLeave&&n.onLeave()}},_=t=>{const e=t.target;!o.contains(e)&&e!==o&&r.isActive&&k()};return r.destroy=()=>{r.destroyed=!0,cancelAnimationFrame(g),C(document,"click",_),C(o,"pointerdown",T),C(o,"pointerenter",T),C(o,"pointermove",Y),C(o,"touchmove",X),C(o,"pointerleave",k),C(o,"pointerup",k),C(o,"lostpointercapture",k),delete e.__atropos__},"string"==typeof e&&(e=$(document,e)),e&&(e.__atropos__||(void 0!==o?"string"==typeof o&&(o=$(document,o)):o=e,s=a?e.parentNode.host:e,Object.assign(r,{el:e}),i=$(e,".atropos-rotate"),p=$(e,".atropos-scale"),c=$(e,".atropos-inner"),e.__atropos__=r)),e&&o&&(n.shadow&&(()=>{let t;h=$(e,".atropos-shadow"),h||(h=document.createElement("span"),h.classList.add("atropos-shadow"),t=!0),M(h,`translate3d(0,0,-${n.shadowOffset}px) scale(${n.shadowScale})`),t&&i.appendChild(h)})(),n.highlight&&(()=>{let t;u=$(e,".atropos-highlight"),u||(u=document.createElement("span"),u.classList.add("atropos-highlight"),t=!0),M(u,"translate3d(0,0,0)"),t&&c.appendChild(u)})(),n.rotateTouch&&("string"==typeof n.rotateTouch?e.classList.add(`atropos-rotate-touch-${n.rotateTouch}`):e.classList.add("atropos-rotate-touch")),$(s,"[data-atropos-opacity]")&&A({opacityOnly:!0}),L(document,"click",_),L(o,"pointerdown",T),L(o,"pointerenter",T),L(o,"pointermove",Y),L(o,"touchmove",X),L(o,"pointerleave",k),L(o,"pointerup",k),L(o,"lostpointercapture",k),n.alwaysActive&&(S(),O())),r}const styles=".atropos{position:relative;display:block;perspective:1200px;transform:translate3d(0,0,0)}.atropos-rotate-scroll-x,.atropos-rotate-scroll-y,.atropos-rotate-touch{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;user-select:none}.atropos-rotate-touch-scroll-y{touch-action:pan-y}.atropos-rotate-touch-scroll-x{touch-action:pan-x}.atropos-rotate-touch{touch-action:none}.atropos-rotate,.atropos-scale{width:100%;height:100%;transform-style:preserve-3d;transition-property:transform;display:block}.atropos-highlight,.atropos-shadow{position:absolute;pointer-events:none;transition-property:transform,opacity;display:block;opacity:0}.atropos-shadow{z-index:-1;background:#000;left:0;top:0;width:100%;height:100%;filter:blur(30px)}.atropos-highlight{left:-50%;top:-50%;width:200%;height:200%;background-image:radial-gradient(circle at 50%,rgba(255,255,255,.25),transparent 50%);z-index:0}.atropos-rotate{position:relative}.atropos-inner{width:100%;height:100%;position:relative;overflow:hidden;transform-style:preserve-3d;transform:translate3d(0,0,0);display:block}.atropos-active{z-index:1}.atropos-active .atropos-shadow{opacity:1!important}::slotted([data-atropos-offset]),[data-atropos-offset]{transition-property:transform}[data-atropos-opacity]{transition-property:opacity}::slotted([data-atropos-offset][data-atropos-opacity]),[data-atropos-offset][data-atropos-opacity]{transition-property:transform,opacity}";class AtroposComponent extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"})}connectedCallback(){this.init()}disconnectedCallback(){this.destroy()}init(){const t={...defaults},e={};Object.keys(t).forEach((o=>{const a=o.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`)),s=this.getAttribute(a);if(null===s)e[o]=t[o];else switch(typeof t[o]){case"boolean":e[o]="false"!==s;break;case"number":e[o]=isNaN(parseFloat(s,10))?t[o]:parseFloat(s,10);break;default:e[o]=s}}));const o=this.cls("atropos-inner",e.innerClass),a=document.createElement("div");if(a.classList.add("atropos"),a.innerHTML=`\n <div class="atropos-scale" part="scale">\n <div class="atropos-rotate" part="rotate">\n <div class="${o}" part="inner">\n <slot></slot>\n </div>\n <slot name="rotate"></slot>\n </div>\n <slot name="scale"></slot>\n </div>\n <slot name="root"></slot>\n `,this.shadow.innerHTML="","undefined"!=typeof CSSStyleSheet&&this.shadow.adoptedStyleSheets){const t=new CSSStyleSheet;t.replaceSync(styles),this.shadow.adoptedStyleSheets=[t]}else{const t=document.createElement("style");t.rel="stylesheet",t.textContent=styles,this.shadow.appendChild(t)}this.shadow.appendChild(a),this.atroposRef=Atropos({el:a,isComponent:!0,...e,onEnter:()=>{this.dispatchEvent(new CustomEvent("enter"))},onLeave:()=>{this.dispatchEvent(new CustomEvent("leave"))},onRotate:(...t)=>{this.dispatchEvent(new CustomEvent("rotate",{detail:t}))}})}destroy(){this.atroposInstance&&(this.atroposInstance.destroy(),this.atroposInstance=null)}cls(...t){return t.filter((t=>!!t)).join(" ")}}export{AtroposComponent as default};