Changes carousel auto scroll to pause when interaction occurs
This commit is contained in:
@@ -72,7 +72,7 @@ export function ChatWidget({ onAction }: ChatWidgetProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
if (!hasInteracted.current) setShowNudge(true)
|
if (!hasInteracted.current) setShowNudge(true)
|
||||||
}, 12_000)
|
}, 5_000)
|
||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -722,7 +722,7 @@ export function ChatWidget({ onAction }: ChatWidgetProps) {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={prefersReducedMotion ? { opacity: 1 } : { opacity: 0, y: 6 }}
|
initial={prefersReducedMotion ? { opacity: 1 } : { opacity: 0, y: 6 }}
|
||||||
animate={{ opacity: 1, y: 0, transition: motionSafeTransition(0.25, 'easeOut') }}
|
animate={{ opacity: 1, y: 0, transition: motionSafeTransition(0.25, 'easeOut') }}
|
||||||
exit={prefersReducedMotion ? { opacity: 1 } : { opacity: 0, y: 4, transition: { duration: 0.2, ease: 'easeIn' } }}
|
exit={{ opacity: 0, transition: { duration: 0 } }}
|
||||||
className="fixed z-[101] right-4 md:right-6 pointer-events-none"
|
className="fixed z-[101] right-4 md:right-6 pointer-events-none"
|
||||||
style={{
|
style={{
|
||||||
/* Position above button: button-bottom + button-height + gap */
|
/* Position above button: button-bottom + button-height + gap */
|
||||||
|
|||||||
@@ -539,9 +539,26 @@ function ContinuousScrollCarousel() {
|
|||||||
: false,
|
: false,
|
||||||
)
|
)
|
||||||
const resumeTimeoutRef = useRef<number>(0)
|
const resumeTimeoutRef = useRef<number>(0)
|
||||||
|
const resumeTimestampRef = useRef<number | null>(null)
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
const cardRefs = useRef<Map<number, HTMLDivElement>>(new Map())
|
const cardRefs = useRef<Map<number, HTMLDivElement>>(new Map())
|
||||||
|
|
||||||
|
const RESUME_DELAY_MS = 10000
|
||||||
|
const RAMP_DURATION_MS = 2000
|
||||||
|
|
||||||
|
const pauseCarousel = useCallback(() => {
|
||||||
|
isPausedRef.current = true
|
||||||
|
window.clearTimeout(resumeTimeoutRef.current)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const scheduleResume = useCallback(() => {
|
||||||
|
window.clearTimeout(resumeTimeoutRef.current)
|
||||||
|
resumeTimeoutRef.current = window.setTimeout(() => {
|
||||||
|
isPausedRef.current = false
|
||||||
|
resumeTimestampRef.current = performance.now()
|
||||||
|
}, RESUME_DELAY_MS)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const jumpByCards = useCallback((direction: 1 | -1) => {
|
const jumpByCards = useCallback((direction: 1 | -1) => {
|
||||||
const trackEl = trackRef.current
|
const trackEl = trackRef.current
|
||||||
const firstSetEl = firstSetRef.current
|
const firstSetEl = firstSetRef.current
|
||||||
@@ -553,9 +570,7 @@ function ContinuousScrollCarousel() {
|
|||||||
const cardWidth = (viewportWidth - totalGap) / cardsPerView
|
const cardWidth = (viewportWidth - totalGap) / cardsPerView
|
||||||
const jumpPx = cardWidth + gap
|
const jumpPx = cardWidth + gap
|
||||||
|
|
||||||
// Pause auto-scroll
|
pauseCarousel()
|
||||||
isPausedRef.current = true
|
|
||||||
window.clearTimeout(resumeTimeoutRef.current)
|
|
||||||
|
|
||||||
// Apply CSS transition for smooth jump
|
// Apply CSS transition for smooth jump
|
||||||
if (!prefersReducedMotion) {
|
if (!prefersReducedMotion) {
|
||||||
@@ -579,11 +594,8 @@ function ContinuousScrollCarousel() {
|
|||||||
trackEl.addEventListener('transitionend', transitionEnd, { once: true })
|
trackEl.addEventListener('transitionend', transitionEnd, { once: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resume auto-scroll after 6s
|
scheduleResume()
|
||||||
resumeTimeoutRef.current = window.setTimeout(() => {
|
}, [viewportWidth, prefersReducedMotion, pauseCarousel, scheduleResume])
|
||||||
isPausedRef.current = false
|
|
||||||
}, 6000)
|
|
||||||
}, [viewportWidth, prefersReducedMotion])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -629,9 +641,19 @@ function ContinuousScrollCarousel() {
|
|||||||
const deltaSeconds = (timestamp - lastTime) / 1000
|
const deltaSeconds = (timestamp - lastTime) / 1000
|
||||||
lastTime = timestamp
|
lastTime = timestamp
|
||||||
if (!isPausedRef.current) {
|
if (!isPausedRef.current) {
|
||||||
|
let speedMultiplier = 1
|
||||||
|
if (resumeTimestampRef.current !== null) {
|
||||||
|
const elapsed = timestamp - resumeTimestampRef.current
|
||||||
|
if (elapsed < RAMP_DURATION_MS) {
|
||||||
|
const t = elapsed / RAMP_DURATION_MS
|
||||||
|
speedMultiplier = 1 - Math.pow(1 - t, 2)
|
||||||
|
} else {
|
||||||
|
resumeTimestampRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
const setWidth = firstSetEl.offsetWidth
|
const setWidth = firstSetEl.offsetWidth
|
||||||
if (setWidth > 0) {
|
if (setWidth > 0) {
|
||||||
offsetRef.current += speedPxPerSecond * deltaSeconds
|
offsetRef.current += speedPxPerSecond * speedMultiplier * deltaSeconds
|
||||||
if (offsetRef.current >= setWidth) offsetRef.current -= setWidth
|
if (offsetRef.current >= setWidth) offsetRef.current -= setWidth
|
||||||
trackEl.style.transform = `translate3d(-${offsetRef.current}px, 0, 0)`
|
trackEl.style.transform = `translate3d(-${offsetRef.current}px, 0, 0)`
|
||||||
}
|
}
|
||||||
@@ -655,10 +677,6 @@ function ContinuousScrollCarousel() {
|
|||||||
return 214
|
return 214
|
||||||
}, [viewportWidth])
|
}, [viewportWidth])
|
||||||
|
|
||||||
const setPaused = (value: boolean) => {
|
|
||||||
isPausedRef.current = value
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleArrowKey = useCallback((currentIndex: number, direction: -1 | 1) => {
|
const handleArrowKey = useCallback((currentIndex: number, direction: -1 | 1) => {
|
||||||
const nextIndex = (currentIndex + direction + investigations.length) % investigations.length
|
const nextIndex = (currentIndex + direction + investigations.length) % investigations.length
|
||||||
cardRefs.current.get(nextIndex)?.focus()
|
cardRefs.current.get(nextIndex)?.focus()
|
||||||
@@ -702,12 +720,12 @@ function ContinuousScrollCarousel() {
|
|||||||
<div
|
<div
|
||||||
ref={viewportRef}
|
ref={viewportRef}
|
||||||
style={{ overflow: 'hidden' }}
|
style={{ overflow: 'hidden' }}
|
||||||
onMouseEnter={() => setPaused(true)}
|
onMouseEnter={() => pauseCarousel()}
|
||||||
onMouseLeave={() => setPaused(false)}
|
onMouseLeave={() => scheduleResume()}
|
||||||
onFocusCapture={() => setPaused(true)}
|
onFocusCapture={() => pauseCarousel()}
|
||||||
onBlurCapture={(event) => {
|
onBlurCapture={(event) => {
|
||||||
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
|
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) {
|
||||||
setPaused(false)
|
scheduleResume()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user