You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为重新渲染的React组件应用CSS动画(滑动图片效果)

复刻滑动图片效果的React实现问题

我正尝试复刻某网站的滑动图片效果,已经理解了原网站的实现逻辑,但在React中实现时遇到了困难。

我发布了一个示例项目展示当前进展,已解决 - (感谢下方回答,该链接现已正确展示动画效果)

遇到的核心难点是图片动画实现:状态更新时React会重新渲染Section组件,打乱了我给元素应用类名的逻辑。用React Profiler确认过,这些是组件更新而非重新挂载。

最初把Intersection Observer放在Section组件外部,但没生效。一开始用简单的setState,后来改成reducer,想在其他类之后添加active类,但还是没解决问题。现在一筹莫展,希望得到提示和方向指引。

这是基于Next.js的项目,代码如下:

import React, {
    Fragment,
    useState,
    forwardRef,
    useEffect,
    useLayoutEffect,
    useCallback,
    useContext,
    useRef,
    useMemo,
} from 'react';

import cx from 'classnames';
// yarn add classnames


const useIntersect = ({root = null, rootMargin, threshold = 0}) => {
    const [entry, updateEntry] = useState({});
    const [node, setNode] = useState(null);

    const observer = useRef(null);

    useEffect(() => {
        if (observer.current) observer.current.disconnect();

        observer.current = new IntersectionObserver(([entry]) => updateEntry(entry), {
            root,
            rootMargin,
            threshold,
        });

        const {current: currentObserver} = observer;

        if (node) currentObserver.observe(node);

        return () => currentObserver.disconnect();
    }, [node]);

    return [setNode, entry];
};

const Home = () => {
    const initialState = {active: 'teal', current: 'teal'};

    const [state, dispatch] = React.useReducer(reducer, initialState);

    function reducer(state, {section, type}) {
        switch (type) {
            case 'reset':
                return initialState;
            case 'changeSection':
                return {
                    ...state,
                    previous: state?.current,
                    current: section,
                };
            case 'makeActive':
                return {
                    ...state,
                    active: section,
                };
            default:
                return state;
        }
    }

    const activeSection = (section) => {
        dispatch({section: section, type: 'changeSection'});

        setTimeout(() => {
            dispatch({type: 'makeActive', section: section});
        }, 1000);
    };

    const Section = ({sectionID, className, children}) => {
        const [ref, entry] = useIntersect({
            threshold: '0.7',
        });

        useEffect(() => {
            if (state?.current == sectionID) return;

            if (entry.isIntersecting) {
                activeSection(sectionID);
            }
        }, [entry]);

        const childrenWithProps = React.Children.map(children, (child, index) => {
            if (React.isValidElement(child)) {
                if (index == 0)
                    return React.cloneElement(child, {
                        className: cx(
                            'fixed top-0 w-1/2 h-screen transition-all duration-[1000ms] place-items-center place-content-center transform-gpu',
                            {
                                'translate-y-[-100vh]': sectionID != state?.active,
                                'z-40 translate-y-0': sectionID == state?.current && sectionID == state?.active,
                            }
                        ),
                        data: 'observable',
                        id: sectionID,
                    });
            }
            return child;
        });

        return (
            <section className={className} ref={ref}>
                {childrenWithProps}
            </section>
        );
    };

    return (
        <>
            <div className="fixed bottom-0 z-50 w-1/2 opacity-50">
                <pre>{JSON.stringify(state, null, 2)}</pre>
            </div>

            <Section sectionID="teal" className="bg-teal-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=1" />
                </div>

                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>

            <Section sectionID="blue" className="bg-blue-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=2" />
                </div>
                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>

            <Section sectionID="orange" className="bg-orange-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=3" />
                </div>
                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>

            <Section sectionID="pink" className="bg-pink-200">
                <div>
                    <img className="block object-cover w-full h-full" src="http://placekitten.com/800/1600?image=4" />
                </div>

                <div className="flex flex-col justify-center w-1/2 min-h-screen px-8 py-64 ml-auto section_content">
                    <p className="mb-6">
                        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent mollis molestie eros eget
                        ultricies. Mauris tempus odio fermentum, elementum odio a, molestie metus. Nullam id dolor
                        viverra, scelerisque mi nec, volutpat sapien. Aenean ac nibh gravida, congue velit sit amet,
                        ultricies mi. Ut posuere ullamcorper elit, eget faucibus turpis fermentum mattis. Nulla
                        facilisi. Aliquam volutpat maximus vehicula. Nulla commodo dolor vitae euismod condimentum.
                        Maecenas et justo rutrum, varius velit at, facilisis mauris. Maecenas eget eros in dui mollis
                        tempor iaculis eu massa. Nulla ullamcorper finibus cursus.
                    </p>
                </div>
            </Section>
        </>
    );
};

export default Home;

内容的提问来源于stack exchange,提问作者anastymous

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 18:48:24