TypeScript中使用Intersection Observer与useRef出现类型错误求助
解决React+TypeScript中Intersection Observer的类型错误及动画触发问题
问题描述
我有一个运行正常的文本动画,希望添加Intersection Observer,让动画仅在滚动到Box组件时触发。通过React钩子useRef获取目标元素引用并绑定到Box(ref={containerRef}),实现了自定义钩子useElementOnScreen封装观察逻辑,但遇到两个TypeScript错误:
- 在
containerRef?.current处报错:
Argument of type 'IntersectionObserver' is not assignable to parameter of type 'Element'. Type 'IntersectionObserver' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 160 more.
ref={containerRef}触发类型错误:
The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & { component: ElementType<any>; } & SystemProps<Theme> & { children?: ReactNode; component?: ElementType<...> | undefined; ref?: Ref<...> | undefined; sx?: SxProps<...> | undefined; } & CommonProps & Omit<...>'
动画说明:TopAnimateBlock和BottomAnimateBlock的numOfLine属性控制块内行数,BottomAnimateBlock的delayTopLine属性需与TopAnimateBlock的numOfLine值一致,以等待顶部动画完成。
错误原因分析
- useRef类型错误:给
containerRef指定了错误的类型IntersectionObserver | null,它实际应指向DOM元素,正确类型为HTMLDivElement | null(MUI的Box默认渲染div)。 - 动画触发逻辑缺失:原代码未根据
isVisible状态控制动画执行,页面加载时会直接触发动画,不符合“滚动到组件时才触发”的需求。
修改后的完整代码
import { Box, Typography } from '@mui/material'; import React, { useRef, useEffect, useState } from 'react'; import styled, { keyframes } from 'styled-components'; const showTopText = keyframes` 0% { transform: translate3d(0, 100% , 0); } 40%, 60% { transform: translate3d(0, 50%, 0); } 100% { transform: translate3d(0, 0, 0); } `; const showBottomText = keyframes` 0% { transform: translate3d(0, -100%, 0); } 100% { transform: translate3d(0, 0, 0); } `; const Section = styled.section` width: calc(100% + 10vmin); display: flex; flex-flow: column; padding: 2vmin 0; overflow: hidden; &:last-child { border-top: 1vmin solid white; } `; const Block = styled.div<{ numOfLine: number }>` position: relative; `; // 添加isVisible props控制动画是否执行 const TopAnimateBlock = styled(Block)<{ numOfLine: number; isVisible: boolean }>` transform: translateY(calc(100% * ${props => props.numOfLine})); ${props => props.isVisible && ` animation: ${showTopText} calc(0.5s * ${props.numOfLine}) forwards; animation-delay: 0.5s; `} `; const BottomAnimateBlock = styled(Block)<{ numOfLine: number; delayTopLine: number; isVisible: boolean }>` transform: translateY(calc(-100% * ${props => props.numOfLine})); ${props => props.isVisible && ` animation: ${showBottomText} calc(0.5s * ${props.numOfLine}) forwards; animation-delay: calc(0.7s * ${props.delayTopLine}); `} `; const TextStyle = styled.p<{ color: string }>` font-family: Roboto, Arial, sans-serif; font-size: 12vmin; color: ${props => props.color}; `; // 修正自定义钩子的类型定义 const useElementOnScreen = (options: IntersectionObserverInit) => { // 指定ref类型为HTMLDivElement,匹配Box默认渲染的DOM元素 const containerRef = useRef<HTMLDivElement | null>(null); const [isVisible, setIsVisible] = useState(false); const callbackFunction = (entries: IntersectionObserverEntry[]) => { const [entry] = entries; setIsVisible(entry.isIntersecting); }; useEffect(() => { const observer = new IntersectionObserver(callbackFunction, options); if (containerRef.current) observer.observe(containerRef.current); return () => { if (containerRef.current) observer.unobserve(containerRef.current); }; }, [options]); // 移除containerRef依赖,因为ref引用是稳定的 return [containerRef, isVisible] as const; }; export function Details() { const [containerRef, isVisible] = useElementOnScreen({ root: null, rootMargin: '0px', threshold: 1.0, }); return ( <> <Typography>Scroll Down</Typography> {/* ref类型现在匹配,无类型错误 */} <Box ref={containerRef}> <Section> <TopAnimateBlock numOfLine={2} isVisible={isVisible}> <TextStyle color="grey">mimicking</TextStyle> <TextStyle color="white">apple's design</TextStyle> </TopAnimateBlock> </Section> <Section> <BottomAnimateBlock numOfLine={1} delayTopLine={2} isVisible={isVisible}> <TextStyle color="white">for the win!</TextStyle> </BottomAnimateBlock> </Section> </Box> </> ); };
关键修改点说明
- 修正useRef类型:将
containerRef类型从IntersectionObserver | null改为HTMLDivElement | null,确保指向正确的DOM元素。 - 条件触发动画:给动画组件添加
isVisible属性,仅当元素进入视口时才应用动画样式,实现滚动触发效果。 - 优化钩子依赖:移除
useEffect中的containerRef依赖,因为useRef返回的引用在组件生命周期内保持稳定。 - 明确类型定义:为
useElementOnScreen的参数和回调函数添加精准的TypeScript类型,提升代码安全性。
内容的提问来源于stack exchange,提问作者user18943198
相关产品推荐
相关产品推荐

