@blossom-carousel/web
Version:
A native-scroll-first carousel component for the web.
2 lines (1 loc) • 9.93 kB
JavaScript
(function(b,t){typeof exports=="object"&&typeof module<"u"?t(exports):typeof define=="function"&&define.amd?define(["exports"],t):(b=typeof globalThis<"u"?globalThis:b||self,t(b.BlossomCarousel={}))})(this,(function(b){"use strict";const t={scroller:null,end:300,isDragging:!1,scrollerScrollWidth:300,scrollerWidth:300,scrollerScrollHeight:300,scrollerHeight:300,padding:{start:0,end:0},scrollPadding:{start:0,end:0},slidePositions:[],hasSnap:!1,dir:1};function le(e,r,s){return(1-s)*e+s*r}function L(e,r,s,l){return le(e,r,1-Math.exp(Math.log(1-s)*(l/(1e3/60))))}function R(e,r,s){return Math.max(r,Math.min(s,e))}function A(e,r=0){const s=Math.pow(10,r);return Math.round(e*s)/s}function oe(e,r,s){return e+r/(1-s)}const v=.72,M=.12,V=new Event("scrollend",{bubbles:!0,cancelable:!0}),D=(e,r,s)=>{const l=s?new CustomEvent(r,{bubbles:!0,cancelable:!0,detail:s}):new Event(r,{bubbles:!0,cancelable:!0});return e?.dispatchEvent(l),l},T=(e,r)=>D(e,"overscroll",r),se=e=>(e?.dispatchEvent(V),V),ie=(e,r)=>D(e,"scrollsnapchange",r),X=(e,r)=>D(e,"scrollsnapchanging",r),h={positions:[],activePosition:{target:null,x:0}};function ce(e){let r=[],s=0;const l=u=>{if(s++,s>100)return;const d=window.getComputedStyle(u).scrollSnapAlign;if(d!=="none"){r.push({align:d,el:u});return}const g=u.children;if(g.length!==0)for(let y of g)l(y)};l(e);const c=e.getBoundingClientRect(),a=r.map(({el:u,align:d},g)=>{const y=u.getBoundingClientRect(),P=u.clientWidth,m=y.left-c.left+e.scrollLeft;switch(d){case"start":return{target:u,x:m-t.scrollPadding.start,y:0};case"end":return{target:u,x:m+P-t.scrollerWidth+t.scrollPadding.end,y:0};case"center":return{target:u,x:m+P*.5-t.scrollerWidth/2,y:0};default:return null}}).filter(u=>u!==null).reduce((u,d)=>((u.length===0||u[u.length-1].x!==d.x)&&u.push(d),u),[]);h.positions=a}function ae(e,r,s){const l=F(e,r,s);return l.x!==h.activePosition.x&&X(t.scroller,{snapTargetInline:(l||h.activePosition).target,snapTargetBlock:(l||h.activePosition).target}),h.activePosition=l,(R(l.x,Math.min((t.scrollerScrollWidth-t.scrollerWidth)*t.dir,0),Math.max((t.scrollerScrollWidth-t.scrollerWidth)*t.dir,0))-e)*(1-v)*(1/v)}function F(e,r,s){const l=oe(e,r,s);return h.positions.length?h.positions.reduce((c,a)=>Math.abs(a.x-l)<Math.abs(c.x-l)?a:c):{target:null,x:R(l,Math.min(t.end,0),Math.max(t.end,0)),y:0}}function ue(){ie(t.scroller,{snapTargetInline:h.activePosition.target,snapTargetBlock:h.activePosition.target})}function de(e,r,s){const l=F(e,r,s);l.x!==h.activePosition.x&&(h.activePosition=l,X(t.scroller,{snapTargetInline:(l||h.activePosition).target,snapTargetBlock:(l||h.activePosition).target}))}const fe={center:(e,r)=>e+r*.5-t.scrollerWidth/2,end:(e,r)=>e+r-t.scrollerWidth+t.scrollPadding.end,start:(e,r)=>e-t.scrollPadding.start};function O(e,r,s){if(t.hasSnap)return e;const l=fe[s||"start"];return l(e,r)}function Y(e,r){if(!t.scroller)return 0;const s=t.scroller.scrollLeft,l=h.positions.length?h.positions:t.slidePositions;if(e==="prev")for(let c=l.length-1;c>=0;c--){const a=O(l[c].x,l[c].width||0,r);if(a<s-1)return a}else for(let c=0;c<l.length;c++){const a=O(l[c].x,l[c].width||0,r);if(a>s+1)return a}return 0}function he({align:e}={}){const r=Y("prev",e);r&&t.scroller.scrollTo({left:r,behavior:"smooth"})}function ge({align:e}={}){const r=Y("next",e);r&&t.scroller.scrollTo({left:r,behavior:"smooth"})}const pe=(e,r)=>{t.scroller=e;let s=!0;const l={x:0,y:0},c={x:0,y:0},a={x:0,y:0},u=new Proxy({x:0,y:0},{set(n,o,i){return n[o]===i||(n[o]=i,(n.x>=10||n.y>=10)&&(x.value=!0)),!0}}),d=new Proxy({x:!1,y:!1},{set(n,o,i){return n[o]===i||(n[o]=i,n.x||n.y?(e.setAttribute("has-overflow","true"),e.addEventListener("pointerdown",G),e.addEventListener("wheel",Z,{passive:!1})):(e.removeAttribute("has-overflow"),e.removeEventListener("pointerdown",G),e.removeEventListener("wheel",Z))),!0}});let g=null,y=null,P=null,m=null,j;function xe(){e?.setAttribute("blossom-carousel","true"),y=e?.querySelectorAll("a[href]")||null,y?.forEach(i=>{i.addEventListener("click",$)}),window.addEventListener("keydown",_),e.addEventListener("scroll",U),P=new ResizeObserver(z),P.observe(e),m=new MutationObserver(ye),m.observe(e,{attributes:!1,childList:!0,subtree:!1});const n=window.matchMedia("(hover: hover) and (pointer: fine)").matches;t.dir=e.closest('[dir="rtl"]')?-1:1;const{scrollSnapType:o}=window.getComputedStyle(e);t.hasSnap=o!=="none",e.style.setProperty("--snap-type",o),n&&e.style.setProperty("scroll-snap-type","none"),e.setAttribute("has-snap",s?"true":"false"),e.setAttribute("has-repeat",r?.repeat?"true":"false"),j=Pe(i=>{(i===e||e.contains(i))&&(x.value=!1)})}function ve(){e.removeAttribute("blossom-carousel"),P?.disconnect(),m?.disconnect(),g&&cancelAnimationFrame(g),window.removeEventListener("keydown",_),e.removeEventListener("scroll",U),y?.forEach(n=>{n.removeEventListener("click",$)}),j?.()}function $(n){u.x>10&&n.preventDefault()}function z(){if(!e)return;const n="ontouchmove"in window;t.scrollerScrollWidth=e.scrollWidth,t.scrollerWidth=e.clientWidth,t.scrollerScrollHeight=e.scrollHeight,t.scrollerHeight=e.clientHeight;const o=window.getComputedStyle(e);d.x=!n&&t.scrollerScrollWidth>t.scrollerWidth&&["auto","scroll"].includes(o.getPropertyValue("overflow-x")),d.y=!n&&t.scrollerScrollHeight>t.scrollerHeight&&["auto","scroll"].includes(o.getPropertyValue("overflow-y")),t.padding.end=parseInt(o.paddingInlineEnd)||0,t.padding.start=parseInt(o.paddingInlineStart)||0,t.scrollPadding.start=parseInt(o.scrollPaddingInlineStart)||0,t.scrollPadding.end=parseInt(o.scrollPaddingInlineEnd)||0,t.dir=e.closest('[dir="rtl"]')?-1:1,t.end=(t.scrollerScrollWidth-t.scrollerWidth-4)*t.dir,t.hasSnap?ce(e):t.slidePositions=Array.from(e.children).map(i=>{const p=i.getBoundingClientRect(),B=e.getBoundingClientRect(),w=p.left-B.left+e.scrollLeft;return{target:i,x:w-t.scrollPadding.start,y:0,width:p.width,height:p.height}}),r?.repeat&&C(null,null)}function ye(){z()}function U(){if(r?.repeat){C(null,null);return}if(t.isDragging||!e)return;const n=e.scrollLeft;n<0?T(e,{left:n*-1}):n>t.scrollerScrollWidth-t.scrollerWidth&&T(e,{left:n*-1+t.scrollerScrollWidth-t.scrollerWidth})}const f={x:0,y:0};function G(n){e&&(d.x&&(f.x=e.scrollLeft,l.x=n.clientX,a.x=0),d.y&&(f.y=e.scrollTop,l.y=n.clientY,a.y=0),u.x=0,t.isDragging=!0,window.addEventListener("pointermove",J),window.addEventListener("pointerup",Q))}function J(n){if(n.preventDefault(),d.x){const o=l.x-n.clientX;c.x+=o,a.x+=o,l.x=n.clientX,u.x+=Math.abs(o)}if(d.y){const o=l.y-n.clientY;c.y+=o,a.y+=o,l.y=n.clientY,u.y+=Math.abs(o)}}function Q(){window.removeEventListener("pointermove",J),window.removeEventListener("pointerup",Q),t.isDragging=!1,!(u.x<=10)&&(d.x&&(a.x*=2),d.y&&(a.y*=2),a.x=ae(c.x,a.x,v),Ee())}function Z(n){if(Math.abs(n.deltaX)>Math.abs(n.deltaY)){if(x.value=!1,t.isDragging||!e)return;d.x&&(f.x=e.scrollLeft),d.y&&(f.y=e.scrollTop)}}function _(n){["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"].includes(n.key)&&(x.value=!1)}function C(n,o){if(!e)return;const i=o??e.scrollLeft,p=t.padding.start-i,B=i-(t.scrollerScrollWidth-t.scrollerWidth-t.padding.end),w=Array.from(e.children),ee=(Se,ne,We,H)=>{let re=0;const Le=H?-1:1,Ie=H?-(t.scrollerScrollWidth-t.scrollerWidth):t.scrollerScrollWidth-t.scrollerWidth;for(let I=Se;H?I>=ne:I<ne;I+=Le){const Ae=re>We;w[I].style.translate=`${Ae?0:Ie}px 0`,re+=w[I].clientWidth}};if(ee(w.length-1,w.length/2,p,!0),ee(0,w.length/2,B,!1),t.isDragging)return;const te=i>t.end?4:i<4?t.end:null;te&&(W=!0,e.scrollTo({left:te,behavior:"instant"}))}function N(n){x.value&&n.stopPropagation()}const x=new Proxy({value:!1},{set(n,o,i){return s=!i,n[o]===i?!0:e?(e.setAttribute("has-snap",s?"true":"false"),i&&!x.value?(k=performance.now(),d.x&&(c.x=e.scrollLeft),d.y&&(c.y=e.scrollTop),e.addEventListener("scrollend",N,{capture:!0,passive:!1}),g||(g=requestAnimationFrame(K))):i||(g&&cancelAnimationFrame(g),g=null,e.removeEventListener("scrollend",N)),n[o]=i,!0):!1}});let E=0,k=0;function K(n){g=requestAnimationFrame(K),E=n-k,e&&(d.x&&(a.x*=v,t.isDragging?f.x=L(f.x,c.x,v,E):(c.x+=a.x,f.x=L(f.x,c.x,M,E))),d.y&&(a.y*=v,t.isDragging?f.y=L(f.y,c.y,v,E):(c.y+=a.y,f.y=L(f.y,c.y,M,E))),r?.repeat&&(f.x>t.end&&(f.x=c.x=4),f.x<4&&(f.x=c.x=t.end)),W=!0,e.scrollTo({left:f.x,top:f.y,behavior:"instant"}),t.isDragging&&t.hasSnap&&de(c.x,a.x,v),!t.isDragging&&A(a.x,12)===0&&(x.value=!1,se(e),t.hasSnap&&ue()),r?.repeat?C(null,f.x):me(A(f.x,2)),k=n)}let S=0;function me(n){if(!e)return;const o=t.end;let i=0;if(n*t.dir<=0?i=t.isDragging?n*-.2:0:n*t.dir>o*t.dir&&(i=t.isDragging?(n-o)*-.2:0),S=L(S,i,t.isDragging?.8:M,E),Math.abs(S)>.01){if(T(e,{left:S}).defaultPrevented)return;e.style.transform=`translateX(${A(S,3)}px)`;return}e.style.transform="",S=0}let W=!1;const we=e.scrollTo.bind(e);e.scrollTo=function(...n){W===!0||(x.value=!1),W=!1,we(...n)};const be=e.scrollBy.bind(e);e.scrollBy=function(...n){W===!0||(x.value=!1),W=!1,be(...n)};function Pe(n){const o=[],i=Element.prototype.scrollIntoView;return i&&(Element.prototype.scrollIntoView=function(p){return n(this,"scrollIntoView",[p]),i.call(this,p)},o.push(()=>{Element.prototype.scrollIntoView=i})),()=>o.forEach(p=>p())}function Ee(){const n=o=>{o.preventDefault(),o.stopPropagation(),window.removeEventListener("click",n,!0)};window.addEventListener("click",n,!0)}return{snap:s,hasOverflow:d,init:xe,destroy:ve,prev:he,next:ge}};class q extends HTMLElement{carouselInstance;constructor(){super();const r=this.attachShadow({mode:"open"});this.setAttribute("blossom-carousel","true");const s=document.createElement("slot");r.appendChild(s)}connectedCallback(){this.carouselInstance=pe(this,{repeat:this.hasAttribute("repeat")}),this.carouselInstance.init()}disconnectedCallback(){this.carouselInstance.destroy()}prev(r){this.carouselInstance.prev(r)}next(r){this.carouselInstance.next(r)}}customElements.define("blossom-carousel",q),b.BlossomCarousel=q,Object.defineProperty(b,Symbol.toStringTag,{value:"Module"})}));