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