UNPKG

scroll-img-motion

Version:

A library for smooth scroll-triggered image transitions

2 lines (1 loc) 3.94 kB
import{jsx as t}from"react/jsx-runtime";import{useState as e,useEffect as r,useRef as n,useCallback as c}from"react";import{useAnimationFrame as o,useScroll as i,useTransform as a,useSpring as u}from"framer-motion";function s(t,n){const[c,o]=e(n);return r(()=>{const e=()=>{const e=window.innerWidth<768?360:708,r=window.innerWidth<768?318:800;o({width:e,height:r});const n=t.current;if(!n)return;const c=Math.max(window.devicePixelRatio||1,1),i=Math.floor(e*c),a=Math.floor(r*c);n.width===i&&n.height===a||(n.width=i,n.height=a,n.style.width=`${e}px`,n.style.height=`${r}px`)};return e(),window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[t]),c}async function h(t,e){const r=await fetch(t,{signal:e,credentials:"omit",cache:"force-cache"});if(!r.ok)throw new Error;const n=await r.blob();return"createImageBitmap"in window&&"function"==typeof createImageBitmap?await createImageBitmap(n):await function(t,e){return new Promise((r,n)=>{const c=URL.createObjectURL(t),o=new Image;o.crossOrigin="",o.decoding="async";const i=()=>{URL.revokeObjectURL(c),null==e||e.removeEventListener("abort",a)},a=()=>{i(),n(new DOMException("Aborted","AbortError"))};null==e||e.addEventListener("abort",a,{once:!0}),o.onload=()=>{i(),r(o)},o.onerror=t=>{i(),n(t)},o.src=c})}(n,e)}async function l(t,e){try{return await h(t,e)}catch(r){if(t.endsWith(".webp")){const r=t.slice(0,-5)+".jpg";return await h(r,e)}throw new Error}}function d(t){const e=n(null);return null===e.current&&(e.current=t),e}const f=(t,e={})=>{var n,c;const o=null!==(n=e.maxRequest)&&void 0!==n?n:4,i=null!==(c=e.prefetchWindow)&&void 0!==c?c:20,a=d(new Map),u=d([]),s=d(new Map),h=d(0),f=d(0),w=()=>{if(!(h.current>=o))for(u.current.sort((t,e)=>Math.abs(t-f.current)-Math.abs(e-f.current));h.current<o&&u.current.length;){const e=u.current.shift();if(e<0||e>=t.length)continue;if(a.current.has(e)||s.current.has(e))continue;const r=new AbortController;s.current.set(e,r),h.current++,l(t[e],r.signal).then(t=>a.current.set(e,t)).catch(()=>{}).finally(()=>{s.current.delete(e),h.current=Math.max(0,h.current-1),w()})}};return r(()=>{for(const t of s.current.values())t.abort();s.current.clear(),a.current.clear()},[]),{getBitmap:t=>a.current.get(t),setCenter:e=>{f.current=e;for(const[t,r]of s.current)Math.abs(t-e)>2*i&&(r.abort(),s.current.delete(t),h.current=Math.max(0,h.current-1));const r=Math.max(0,e-i),n=Math.min(t.length-1,e+i);for(let t=r;t<=n;t++)a.current.has(t)||s.current.has(t)||u.current.includes(t)||u.current.push(t);w()}}};function w(t,e,r,n,c){const o=t.current;if(!o||!e)return;const i=o.getContext("2d");if(!i)return;if(n&&null!=c&&n.current===c)return;n&&(n.current=null!=c?c:null);const a=Math.max(window.devicePixelRatio||1,1);i.setTransform(a,0,0,a,0,0);const u="naturalWidth"in e?e.naturalWidth:e.width,s="naturalHeight"in e?e.naturalHeight:e.height,h=r.width,l=r.height,d=u/s;let f,w,m=0,g=0;d>h/l?(f=l*d,w=l,m=(h-f)/2):(w=h/d,f=h,g=(l-w)/2),i.clearRect(0,0,h,l),i.drawImage(e,m,g,f,w)}function m({smoothIndex:t,imagesLen:e,draw:r}){const c=n(0),i=n(0);o(n=>{if(n-i.current<16)return;i.current=n;const o=t.get();c.current+=.7*(o-c.current);const a=Math.floor(Math.min(Math.max(0,c.current),e-1));r(a)})}function g({urls:e}){const o=n(null),h=n(null),l=s(h,{width:708,height:800}),{scrollYProgress:d}=i({target:o,offset:["start 90%","end 0%"]}),g=a(d,[0,.9],[0,e.length-1]),p=u(g,{stiffness:1e3,damping:100}),{getBitmap:b,setCenter:x}=f(e,{maxRequest:4,prefetchWindow:20}),M=n(null),v=c(t=>{const e=b(t);w(h,e,l,M,t)},[b,l]);return m({smoothIndex:p,imagesLen:e.length,draw:t=>{x(t),v(t)}}),r(()=>{x(0),v(0)},[x,v]),t("div",{ref:o,style:{height:690,display:"grid",placeItems:"center",background:"#0b0c0e",borderRadius:12},children:t("canvas",{ref:h})})}export{g as ImgScrollCanvas,w as drawImageOnCanvas,l as fetchBitmapWithFallback,s as useCanvasResize,f as useImageSequenceLoader,m as useSmoothAnimationFrame,d as useStableRef};