-
-
Save kiritocode1/aa096025c761b2ac164f8d53642e1a83 to your computer and use it in GitHub Desktop.
Infinite Scrolling lol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use client"; | |
import { Card , Image } from "@nextui-org/react"; | |
import { gsap } from "gsap"; | |
import localFont from "next/font/local"; | |
import { Projects, colour , ProjectShow} from "@/lib/Projects"; | |
import { useGSAP } from "@gsap/react"; | |
import { Observer, ScrollTrigger } from "gsap/all"; | |
import React , { | |
useState,useEffect | |
} from "react"; | |
import { useSpring, animated as a } from "@react-spring/web"; | |
import { createUseGesture, dragAction, pinchAction } from "@use-gesture/react"; | |
const useGesture = createUseGesture([dragAction, pinchAction]); | |
const myFont = localFont({ | |
src: "../../font/MabryPro-Light.woff", | |
}); | |
import { Navbar } from "@/components/navbar"; | |
import Link from "next/link"; | |
export default function ProjectPage () { | |
const [cl, setCl] = useState(colour.RedAndBlack); | |
const [imag , setImag]= useState(Projects[0].Img); | |
useEffect(() => { | |
const handler = (e: Event) => e.preventDefault(); | |
document.addEventListener("gesturestart", handler); | |
document.addEventListener("gesturechange", handler); | |
document.addEventListener("gestureend", handler); | |
return () => { | |
document.removeEventListener("gesturestart", handler); | |
document.removeEventListener("gesturechange", handler); | |
document.removeEventListener("gestureend", handler); | |
}; | |
}, []); | |
const [style, api] = useSpring(() => ({ | |
x: 0, | |
y: 0, | |
scale: 1, | |
rotateZ: 0, | |
})); | |
const ref = React.useRef<HTMLDivElement>(null); | |
useGesture( | |
{ | |
// onHover: ({ active, event }) => console.log('hover', event, active), | |
// onMove: ({ event }) => console.log('move', event), | |
onDrag: ({ pinching, cancel, offset: [x, y], ...rest }) => { | |
if (pinching) return cancel(); | |
api.start({ x, y }); | |
}, | |
onPinch: ({ | |
origin: [ox, oy], | |
first, | |
movement: [ms], | |
offset: [s, a], | |
memo, | |
}) => { | |
if (first) { | |
const { width, height, x, y } = ref.current!.getBoundingClientRect(); | |
const tx = ox - (x + width / 2); | |
const ty = oy - (y + height / 2); | |
memo = [style.x.get(), style.y.get(), tx, ty]; | |
} | |
const x = memo[0] - (ms - 1) * memo[2]; | |
const y = memo[1] - (ms - 1) * memo[3]; | |
api.start({ scale: s, rotateZ: a, x, y }); | |
return memo; | |
}, | |
}, | |
{ | |
target: ref, | |
drag: { from: () => [style.x.get(), style.y.get()] }, | |
pinch: { scaleBounds: { min: 0.5, max: 2 }, rubberband: true }, | |
}, | |
); | |
useGSAP(() => { | |
gsap.registerPlugin(Observer); | |
gsap.registerPlugin(ScrollTrigger) | |
const Timey = gsap.timeline(); | |
Timey.fromTo( | |
".come-and-go", | |
{ opacity: 1 }, | |
{ autoAlpha: 0, duration: 1, delay: 3 }, | |
); | |
Timey.fromTo( | |
".Main", | |
{ opacity: 0, paddingTop: "20px" }, | |
{ autoAlpha: 1, duration: 0.2, paddingTop: "0px" }, | |
); | |
function verticalLoop(items, config) { | |
items = gsap.utils.toArray(items); | |
config = config || {}; | |
let onChange = config.onChange, | |
lastIndex = 0, | |
tl = gsap.timeline({ | |
repeat: config.repeat, | |
onUpdate: | |
onChange && | |
function () { | |
let i = tl.closestIndex(); | |
if (lastIndex !== i) { | |
lastIndex = i; | |
onChange(items[i], i); | |
} | |
}, | |
paused: config.paused, | |
defaults: { ease: "none" }, | |
onReverseComplete: () => | |
{tl.totalTime(tl.rawTime() + tl.duration() * 100)}, | |
}), | |
length = items.length, | |
startY = items[0].offsetTop, | |
times = [], | |
heights = [], | |
spaceBefore = [], | |
yPercents = [], | |
curIndex = 0, | |
center = config.center, | |
clone = (obj) => { | |
let result = {}, | |
p; | |
for (p in obj) { | |
result[p] = obj[p]; | |
} | |
return result; | |
}, | |
pixelsPerSecond = (config.speed || 1) * 100, | |
snap = | |
config.snap === false ? (v) => v : gsap.utils.snap(config.snap || 1), // some browsers shift by a pixel to accommodate flex layouts, so for example if width is 20% the first element's width might be 242px, and the next 243px, alternating back and forth. So we snap to 5 percentage points to make things look more natural | |
timeOffset = 0, | |
container = | |
center === true | |
? items[0].parentNode | |
: gsap.utils.toArray(center)[0] || items[0].parentNode, | |
totalHeight, | |
getTotalHeight = () => | |
items[length - 1].offsetTop + | |
(yPercents[length - 1] / 100) * heights[length - 1] - | |
startY + | |
spaceBefore[0] + | |
items[length - 1].offsetHeight * | |
//@ts-ignore | |
gsap.getProperty(items[length - 1], "scaleY") + | |
(parseFloat(config.paddingBottom) || 0), | |
populateHeights = () => { | |
let b1 = container.getBoundingClientRect(), | |
b2; | |
items.forEach((el, i) => { | |
//@ts-ignore | |
heights[i] = parseFloat(gsap.getProperty(el, "height", "px")); | |
//@ts-ignore | |
yPercents[i] = snap( | |
//@ts-ignore | |
(parseFloat(gsap.getProperty(el, "y", "px")) / heights[i]) * 100 + | |
gsap.getProperty(el, "yPercent"), | |
); | |
b2 = el.getBoundingClientRect(); | |
//@ts-ignore | |
spaceBefore[i] = b2.top - (i ? b1.bottom : b1.top); | |
b1 = b2; | |
}); | |
gsap.set(items, { | |
// convert "x" to "xPercent" to make things responsive, and populate the widths/xPercents Arrays to make lookups faster. | |
yPercent: (i) => yPercents[i], | |
}); | |
totalHeight = getTotalHeight(); | |
}, | |
timeWrap, | |
populateOffsets = () => { | |
timeOffset = center | |
? (tl.duration() * (container.offsetWidth / 2)) / totalHeight | |
: 0; | |
center && | |
times.forEach((t, i) => { | |
//@ts-ignore | |
times[i] = timeWrap( | |
tl.labels["label" + i] + | |
(tl.duration() * heights[i]) / 2 / totalHeight - | |
timeOffset, | |
); | |
}); | |
}, | |
getClosest = (values, value, wrap) => { | |
let i = values.length, | |
closest = 1e10, | |
index = 0, | |
d; | |
while (i--) { | |
d = Math.abs(values[i] - value); | |
if (d > wrap / 2) { | |
d = wrap - d; | |
} | |
if (d < closest) { | |
closest = d; | |
index = i; | |
} | |
} | |
return index; | |
}, | |
populateTimeline = () => { | |
let i, item, curY, distanceToStart, distanceToLoop; | |
tl.clear(); | |
for (i = 0; i < length; i++) { | |
item = items[i]; | |
curY = (yPercents[i] / 100) * heights[i]; | |
distanceToStart = item.offsetTop + curY - startY + spaceBefore[0]; | |
//@ts-ignore | |
distanceToLoop = | |
//@ts-ignore | |
distanceToStart + heights[i] * gsap.getProperty(item, "scaleY"); | |
tl.to( | |
item, | |
{ | |
yPercent: snap(((curY - distanceToLoop) / heights[i]) * 100), | |
duration: distanceToLoop / pixelsPerSecond, | |
}, | |
0, | |
) | |
.fromTo( | |
item, | |
{ | |
yPercent: snap( | |
((curY - distanceToLoop + totalHeight) / heights[i]) * 100, | |
), | |
}, | |
{ | |
yPercent: yPercents[i], | |
duration: | |
(curY - distanceToLoop + totalHeight - curY) / | |
pixelsPerSecond, | |
immediateRender: false, | |
}, | |
distanceToLoop / pixelsPerSecond, | |
) | |
.add("label" + i, distanceToStart / pixelsPerSecond); | |
//@ts-ignore | |
times[i] = distanceToStart / pixelsPerSecond; | |
} | |
timeWrap = gsap.utils.wrap(0, tl.duration()); | |
}, | |
refresh = (deep) => { | |
let progress = tl.progress(); | |
tl.progress(0, true); | |
populateHeights(); | |
deep && populateTimeline(); | |
populateOffsets(); | |
deep && tl.draggable | |
? tl.time(times[curIndex], true) | |
: tl.progress(progress, true); | |
}, | |
proxy; | |
gsap.set(items, { y: 0 }); | |
populateHeights(); | |
populateTimeline(); | |
populateOffsets(); | |
window.addEventListener("resize", () => refresh(true)); | |
function toIndex(index, vars) { | |
vars = clone(vars); | |
Math.abs(index - curIndex) > length / 2 && | |
(index += index > curIndex ? -length : length); // always go in the shortest direction | |
let newIndex = gsap.utils.wrap(0, length, index), | |
time = times[newIndex]; | |
if (time > tl.time() !== index > curIndex) { | |
// if we're wrapping the timeline's playhead, make the proper adjustments | |
//@ts-ignore | |
time += tl.duration() * (index > curIndex ? 1 : -1); | |
} | |
if (vars.revolutions) { | |
//@ts-ignore | |
time += tl.duration() * Math.round(vars.revolutions); | |
delete vars.revolutions; | |
} | |
if (time < 0 || time > tl.duration()) { | |
vars.modifiers = { time: timeWrap }; | |
} | |
curIndex = newIndex; | |
vars.overwrite = true; | |
gsap.killTweensOf(proxy); | |
return tl.tweenTo(time, vars); | |
} | |
tl.elements = items; | |
tl.next = (vars) => toIndex(curIndex + 1, vars); | |
tl.previous = (vars) => toIndex(curIndex - 1, vars); | |
tl.current = () => curIndex; | |
tl.toIndex = (index, vars) => toIndex(index, vars); | |
tl.closestIndex = (setCurrent) => { | |
let index = getClosest(times, tl.time(), tl.duration()); | |
setCurrent && (curIndex = index); | |
return index; | |
}; | |
tl.times = times; | |
tl.progress(1, true).progress(0, true); // pre-render for performance | |
if (config.reversed) { | |
//@ts-ignore | |
tl.vars.onReverseComplete(); | |
tl.reverse(); | |
} | |
if (config.draggable && typeof Draggable === "function") { | |
proxy = document.createElement("div"); | |
let wrap = gsap.utils.wrap(0, 1), | |
ratio, | |
startProgress, | |
draggable, | |
dragSnap, | |
align = () => | |
tl.progress( | |
wrap(startProgress + (draggable.startY - draggable.y) * ratio), | |
), | |
syncIndex = () => tl.closestIndex(true); | |
typeof InertiaPlugin === "undefined" && | |
console.warn( | |
"InertiaPlugin required for momentum-based scrolling and snapping. https://greensock.com/club", | |
); | |
draggable = Draggable.create(proxy, { | |
trigger: items[0].parentNode, | |
type: "y", | |
onPressInit() { | |
gsap.killTweensOf(tl); | |
startProgress = tl.progress(); | |
//@ts-ignore | |
refresh(); | |
ratio = 1 / totalHeight; | |
gsap.set(proxy, { y: startProgress / -ratio }); | |
}, | |
onDrag: () => { | |
align(); | |
}, | |
onThrowUpdate: () => { | |
align(); | |
} , | |
inertia: true, | |
snap: (value) => { | |
let time = -(value * ratio) * tl.duration(), | |
wrappedTime = timeWrap(time), | |
snapTime = times[getClosest(times, wrappedTime, tl.duration())], | |
dif = snapTime - wrappedTime; | |
Math.abs(dif) > tl.duration() / 2 && | |
(dif += dif < 0 ? tl.duration() : -tl.duration()); | |
return (time + dif) / tl.duration() / -ratio; | |
}, | |
onRelease: syncIndex, | |
onThrowComplete: syncIndex, | |
})[0]; | |
tl.draggable = draggable; | |
} | |
tl.closestIndex(true); | |
onChange && onChange(items[curIndex], curIndex); | |
return tl; | |
} | |
let a = verticalLoop(".wheel1 .box1", { | |
reverse:-1, | |
paused: true, | |
center: true, | |
draggable: true, // I'm just being fancy | |
inertia: true, // even fancier | |
}); | |
gsap.fromTo(".box1",{opacity:0.2},{ | |
scrolltrigger: { | |
trigger: ".box1", | |
markers:true , | |
start: "top center", | |
scrub:true | |
}, | |
opacity:1 | |
}) | |
Observer.create({ | |
target:window, | |
onUp: () => { | |
a.previous(); | |
}, | |
onDown: () => { | |
a.next(); | |
}, | |
}); | |
}); | |
return ( | |
<main | |
className={ | |
"bg-[#ff0000] min-h-screen w-full flex justify-center items-center text-[#000000] relative select-none" + | |
myFont.className | |
}> | |
<div className="text-4xl font-extrabold md:text-9xl come-and-go opacity-0 absolute"> | |
P R O J E C T S | |
</div> | |
<section | |
className={ | |
"Main w-full h-screen overflow-hidden ease-soft-spring duration-250 " + | |
cl | |
}> | |
<Navbar /> | |
<div className=" overflow-scroll no-scrollbar w-full h-screen wheel1 ml-2 "> | |
{Projects.map((project, index) => ( | |
<div | |
key={index} | |
className={`pl-6 text-7xl box1 cursor-move my-4 select-none font-extrabold flex items-center group `} | |
onMouseEnter={() => { | |
setCl(project.color!); | |
setImag(project.Img); | |
}} | |
onMouseLeave={() => { | |
setCl(colour.RedAndBlack); | |
}} | |
> | |
<Link className="group-hover:opacity-100 opacity-50" href={project.PageLink}> | |
{project.Title} | |
</Link> | |
<span className="text-xl group-hover:opacity-100 opacity-0">{`[${project.Role}]`}</span> | |
</div> | |
))} | |
</div> | |
<a.div | |
ref={ref} | |
style={style} | |
className={ | |
"absolute inset-y-0 right-0 z-40 flex items-center justify-center select-none" | |
}> | |
<Card className="col-span-12 sm:col-span-4 h-[300px]" shadow="lg" isHoverable isPressable> | |
<Image | |
src={imag.src} | |
alt="hello" | |
removeWrapper | |
className="object-cover z-0 w-full h-full pointer-events-none" | |
/> | |
</Card> | |
</a.div> | |
</section> | |
</main> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment