feat: enhance CategoryShowcaseScroll with manual scroll locking and improved layout adjustments
This commit is contained in:
parent
8457493f49
commit
79f7f9e579
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState, useCallback } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { motion, useScroll, useTransform } from 'framer-motion';
|
import { motion, useScroll, useTransform } from 'framer-motion';
|
||||||
@ -157,9 +157,12 @@ function CategorySticky() {
|
|||||||
offset: ['start start', 'end end'],
|
offset: ['start start', 'end end'],
|
||||||
});
|
});
|
||||||
const [activeIdx, setActiveIdx] = useState(0);
|
const [activeIdx, setActiveIdx] = useState(0);
|
||||||
|
const manualLockUntil = useRef<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return scrollYProgress.on('change', (v) => {
|
return scrollYProgress.on('change', (v) => {
|
||||||
|
/* honor manual click for a short window so scroll doesn't override */
|
||||||
|
if (Date.now() < manualLockUntil.current) return;
|
||||||
const idx = Math.min(
|
const idx = Math.min(
|
||||||
CATEGORIES.length - 1,
|
CATEGORIES.length - 1,
|
||||||
Math.max(0, Math.floor(v * CATEGORIES.length))
|
Math.max(0, Math.floor(v * CATEGORIES.length))
|
||||||
@ -168,6 +171,12 @@ function CategorySticky() {
|
|||||||
});
|
});
|
||||||
}, [scrollYProgress]);
|
}, [scrollYProgress]);
|
||||||
|
|
||||||
|
const handlePick = useCallback((i: number) => {
|
||||||
|
/* swap in place, no scroll. Lock scroll listener for 1.2s. */
|
||||||
|
manualLockUntil.current = Date.now() + 1200;
|
||||||
|
setActiveIdx(i);
|
||||||
|
}, []);
|
||||||
|
|
||||||
/* progress bar height */
|
/* progress bar height */
|
||||||
const progressHeight = useTransform(scrollYProgress, [0, 1], ['0%', '100%']);
|
const progressHeight = useTransform(scrollYProgress, [0, 1], ['0%', '100%']);
|
||||||
|
|
||||||
@ -176,17 +185,16 @@ function CategorySticky() {
|
|||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
/* 65vh per category — total ~520vh feels responsive, not endless */
|
/* ~50vh per category scroll budget on top of sticky height */
|
||||||
height: `${CATEGORIES.length * 65}vh`,
|
height: `calc(100svh + ${CATEGORIES.length * 50}vh)`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: '5.5rem',
|
top: '5.5rem',
|
||||||
/* fill the viewport — no empty area below the panel during sticky */
|
/* fill the entire viewport — no void below the panel */
|
||||||
height: 'calc(100svh - 6.5rem)',
|
height: 'calc(100svh - 6.5rem)',
|
||||||
maxHeight: '780px',
|
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: 'minmax(0, 0.95fr) minmax(0, 1.05fr)',
|
gridTemplateColumns: 'minmax(0, 0.95fr) minmax(0, 1.05fr)',
|
||||||
gap: 'clamp(1.5rem, 3vw, 2.5rem)',
|
gap: 'clamp(1.5rem, 3vw, 2.5rem)',
|
||||||
@ -226,13 +234,7 @@ function CategorySticky() {
|
|||||||
<button
|
<button
|
||||||
key={c.id}
|
key={c.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => handlePick(i)}
|
||||||
const el = containerRef.current;
|
|
||||||
if (!el) return;
|
|
||||||
const rect = el.getBoundingClientRect();
|
|
||||||
const targetY = window.scrollY + rect.top + (rect.height * (i + 0.5)) / CATEGORIES.length;
|
|
||||||
window.scrollTo({ top: targetY - window.innerHeight / 2, behavior: 'smooth' });
|
|
||||||
}}
|
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user