Skip to content

Instantly share code, notes, and snippets.

@kiritocode1
Created January 14, 2024 10:21
Show Gist options
  • Save kiritocode1/aa096025c761b2ac164f8d53642e1a83 to your computer and use it in GitHub Desktop.
Save kiritocode1/aa096025c761b2ac164f8d53642e1a83 to your computer and use it in GitHub Desktop.
Infinite Scrolling lol
"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