如何在Preact中测量轮播卡片的X轴位移并判断可见性?
卡片移动时X轴数据不更新问题解决
我尝试测量卡片在父容器宽度范围内的移动情况,但点击按钮时X轴数据未更新。核心需求是当Client组件移出屏幕视野时执行特定操作,通过useRect钩子中的getBoundingClientRect方法实现,推测问题出在钩子依赖配置错误或ref传递失败。
相关代码片段
App.tsx
import { useState, useEffect } from 'preact/hooks' import { createRef } from 'preact/compat'; import './app.css' import Client from './components/Client'; import { useRect } from './hooks/useRect'; export function App() { const [translate, setTranslate] = useState<number>(0) function moveCard(prev: number) { setTranslate(prev += 20) } const contentRef = createRef<HTMLDivElement>(); const carouselRect = useRect(contentRef) const {right: carouselEndPoint} = carouselRect; return ( <> <button class='bg-slate-300 absolute top-2 left-2 ' onClick={() => { moveCard(translate) }}> Click to call function</button> <div ref={contentRef} className="grid grid-cols-4 bg-slate-200 gap-3 grid-flow-col w-4/5 mx-auto overflow-clip"> <Client carouselEndPoint={carouselEndPoint} translate={`${translate}%`} setTranslate={setTranslate} name="1"/> </div> </> ) }
Client.tsx
import logo from '../assets/preact.svg' import { useEffect } from 'preact/hooks' import { useRect } from '../hooks/useRect' import { StateUpdater, createRef } from 'preact/compat' export interface ClientProps { name: string translate: string setTranslate: StateUpdater<number> carouselEndPoint: number width?: string | number // contentRef?: RefObject<HTMLDivElement> } function Client({name, translate, setTranslate, carouselEndPoint}: ClientProps) { const contentRef = createRef<HTMLDivElement>(); const val = useRect(contentRef) const {left: clientX} = val; useEffect(() => { console.log(val); if(clientX >= carouselEndPoint) { console.log('hidden'); } }, [translate]) return ( <div className="flex flex-col basis-auto bg-slate-300 items-center drop-shadow-md rounded-md w-full place-content-center transition-transform ease-linear duration-1000 overflow-clip" style={`transform: translateX(${translate})`} ref={contentRef}> {/* client card */} <img className='h-full w-full' src={logo}/> <h3 className="text-5xl">{name}</h3> </div> ) } export default Client;
useRect.tsx
import { RefObject } from 'preact' import { useLayoutEffect, useCallback, useState } from "preact/hooks"; type RectResult = { bottom: number; height: number; left: number; right: number; top: number; width: number; }; function getRect<T extends HTMLElement>(element?: T): RectResult { let rect: RectResult = { bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, }; if (element) rect = element.getBoundingClientRect(); return rect; } export function useRect<T extends HTMLElement>( ref: RefObject<T> ): RectResult { const [rect, setRect] = useState<RectResult>( ref && ref.current ? getRect(ref.current) : getRect() ); const handleResize = useCallback(() => { if (!ref.current) return; setRect(getRect(ref.current)); // Update client rect }, [ref]); useLayoutEffect(() => { const element = ref.current; if (!element) return; handleResize(); if (typeof ResizeObserver === "function") { const resizeObserver = new ResizeObserver(() => handleResize()); resizeObserver.observe(element); return () => { if (!resizeObserver) return; resizeObserver.disconnect(); // resizeObserver = null; }; } else { window.addEventListener("resize", handleResize); // Browser support, remove freely return () => window.removeEventListener("resize", handleResize); } }, [ref.current]); return rect; }
问题分析与修复
核心问题
- ResizeObserver不监听位置变化:
useRect钩子依赖ResizeObserver,但它仅监听元素尺寸变化,不会响应transform导致的位置移动,卡片平移时rect数据不会自动更新。 - useLayoutEffect依赖错误:
useRect中useLayoutEffect的依赖是ref.current,但createRef的current属性变化不会触发组件重渲染,钩子无法感知元素位置变化。 - useEffect依赖不全:Client组件的
useEffect仅监听translate,但clientX和carouselEndPoint变化时不会重新执行判断逻辑。 - 状态更新闭包陷阱:
moveCard直接使用当前translate值更新状态,多次点击会导致状态更新不连续。
修复步骤
1. 修改useRect钩子,支持手动更新
让钩子返回rect数据和手动更新方法,用于主动触发位置数据刷新:
// useRect.tsx 修改后 export function useRect<T extends HTMLElement>( ref: RefObject<T> ): [RectResult, () => void] { const [rect, setRect] = useState<RectResult>( ref && ref.current ? getRect(ref.current) : getRect() ); const updateRect = useCallback(() => { if (!ref.current) return; setRect(getRect(ref.current)); }, [ref]); const handleResize = useCallback(() => { updateRect(); }, [updateRect]); useLayoutEffect(() => { const element = ref.current; if (!element) return; updateRect(); if (typeof ResizeObserver === "function") { const resizeObserver = new ResizeObserver(handleResize); resizeObserver.observe(element); return () => resizeObserver.disconnect(); } else { window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); } }, [ref, updateRect]); return [rect, updateRect]; }
2. 修复Client组件逻辑
在translate变化时手动更新位置数据,并完善useEffect依赖项:
// Client.tsx 修改后 function Client({name, translate, setTranslate, carouselEndPoint}: ClientProps) { const contentRef = createRef<HTMLDivElement>(); const [val, updateRect] = useRect(contentRef) const {left: clientX} = val; useEffect(() => { // 平移后手动刷新位置数据 updateRect(); console.log(val); if(clientX >= carouselEndPoint) { console.log('hidden'); } }, [translate, clientX, carouselEndPoint, updateRect]) return ( <div className="flex flex-col basis-auto bg-slate-300 items-center drop-shadow-md rounded-md w-full place-content-center transition-transform ease-linear duration-1000 overflow-clip" style={`transform: translateX(${translate})`} ref={contentRef}> <img className='h-full w-full' src={logo}/> <h3 className="text-5xl">{name}</h3> </div> ) }
3. 修复App组件状态更新
使用函数式更新避免闭包陷阱:
// App.tsx 修改后 function moveCard() { setTranslate(prev => prev + 20) } // 按钮点击事件简化 <button class='bg-slate-300 absolute top-2 left-2 ' onClick={moveCard}> Click to call function</button>
额外优化
若需判断卡片是否移出父容器而非屏幕,可调整判断逻辑:当卡片的right小于父容器的left,或卡片的left大于父容器的right时,判定为移出父容器视野。
内容的提问来源于stack exchange,提问作者Lauro235
相关产品推荐
相关产品推荐

