Intersection Observer仅适用于Next.js中的最后一个元素。

huangapple go评论77阅读模式
英文:

Intersection observer only works with the last element in Next.js

问题

const AboutMe: NextPage = ({ data }: any) => {
  const targets = useRef<HTMLDivElement[]>(new Array(data.results.length).fill(null));

  useEffect(() => {
    let observer: IntersectionObserver;
    const handleIntersection = (entries: IntersectionObserverEntry[]) => {
      entries.forEach((entry, index) => {
        const targetElement = entry.target as HTMLElement;
        if (entry.isIntersecting) {
          targetElement.style.opacity = "1";
        } else {
          targetElement.style.opacity = "0";
        }
      });
    };

    if (targets.current.every((el) => el !== null)) {
      observer = new IntersectionObserver(handleIntersection, { threshold: 0.5 });
      targets.current.forEach((target) => observer.observe(target as Element));
    }

    return () => {
      if (observer) {
        observer.disconnect();
      }
    };
  }, [targets, data.results]);

  return (
    <main>
      <section className="container px-5 py-24 mx-auto space-y-20">
        {data.results?.map((item: any, index: number) => (
          <div
            className="opacity-0 transition-all duration-500"
            key={item.id}
            ref={(el) => (targets.current[index] = el)}
          >
            <ol>
              <li>{item.properties.content.rich_text[0].plain_text}</li>
            </ol>
          </div>
        ))}
      </section>
    </main>
  );
};
英文:

Intersection observer only works with the last element in Next.js

I want to target all the repeated div elements and put an opacity effect(0 or 1) every time I see them on the viewport, but only the last div element ends up having the opacity effect.

This is my code:

const AboutMe: NextPage = ({ data }: any) => {
  const target = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let observer: IntersectionObserver;
    if (!target.current) return;
      if (target) {
        observer = new IntersectionObserver(
          ([e]) => {
            const target = e.target as HTMLElement;
            if (e.isIntersecting) {
              target.style.opacity = "1";
            } else {
              target.style.opacity = "0";
            }
          },
          { threshold: 0.5 }
        );
        observer.observe(target.current as Element);
        }
    }, [target]);

    return (
      <main>
        <section className="container px-5 py-24 mx-auto space-y-20">
          {data.results?.map((item: any) => {
              return (
                  <div
                      className="opacity-0 transition-all duration-500"
                      key={item.id}
                      ref={target}
                  >
                    <ol>
                      <li>
                        {
                          item.properties.content.rich_text[0]
                              .plain_text
                        }
                      </li>
                    </ol>
                  </div>
                );
            })}
        </section>
      </main>
    );
};

export default AboutMe;


export async function getStaticProps() {
    const options = {
    ...
    };

    const res = await fetch(
        `https://api...`,
        options
    );
    const data = await res.json();

    return {
        props: { data },
    };
}

How can I solve this?

答案1

得分: 1

以下是翻译好的部分:

  1. 你的代码存在一些问题。

  2. 你正在多个 HTML 元素上重复使用相同的 ref 对象。这样是行不通的。你需要将每个元素的 ref 存储在其自己的 ref 对象中。

  3. React 效果在 ref 对象发生变化时不会运行。你当前的效果只在组件挂载时运行一次。

为了保持代码的清晰性,你可以将出现/消失的 div 重构为一个单独的组件,该组件具有自己的 IntersectionObserver

import { NextPage } from "next";
import { useEffect, useRef, useState } from "react";

function Item({ item }: { item: any }) {
  const ref = useRef<HTMLDivElement | null>(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(({ target, isIntersecting }) => {
          if (target === ref.current) {
            setVisible(isIntersecting);
          }
        });
      },
      {
        threshold: 0.5,
      }
    );

    if (ref.current) {
      observer.observe(ref.current);
    }

    return () => {
      observer.disconnect();
    };
  }, []);

  return (
    <div
      ref={ref}
      className={`transition-all duration-500 ${
        visible ? "opacity-100" : "opacity-0"
      }`}
    >
      <ol>
        <li>{item.properties.content.rich_text[0].plain_text}</li>
      </ol>
    </div>
  );
}

const AboutMe: NextPage = ({ data }: any) => {
  return (
    <main>
      <section className="container px-5 py-24 mx-auto space-y-20">
        {data.results?.map((item: any) => (
          <Item key={item.id} item={item} />
        ))}
      </section>
    </main>
  );
};

export default AboutMe;

另外,请考虑用具体的类型定义替换所有的 any 类型,因为在项目中使用 any 类型会破坏使用 TypeScript 的目的。

英文:

There are several issues with your code.

  1. You are re-using the same ref object for multiple HTML elements. This is not going to work this way. You need to store each element's ref in its own ref object.

  2. React effects do not run when ref object changes. Your current effect just happens to run once the component mounts.

To keep the implementation clean, you can refactor your appearing/disappearing divs into a separate component with its own IntersectionObserver.

import { NextPage } from "next";
import { useEffect, useRef, useState } from "react";

function Item({ item }: { item: any }) {
  const ref = useRef<HTMLDivElement | null>(null);
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach(({ target, isIntersecting }) => {
          if (target === ref.current) {
            setVisible(isIntersecting);
          }
        });
      },
      {
        threshold: 0.5,
      }
    );

    if (ref.current) {
      observer.observe(ref.current);
    }

    return () => {
      observer.disconnect();
    };
  }, []);

  return (
    <div
      ref={ref}
      className={`transition-all duration-500 ${
        visible ? "opacity-100" : "opacity-0"
      }`}
    >
      <ol>
        <li>{item.properties.content.rich_text[0].plain_text}</li>
      </ol>
    </div>
  );
}

const AboutMe: NextPage = ({ data }: any) => {
  return (
    <main>
      <section className="container px-5 py-24 mx-auto space-y-20">
        {data.results?.map((item: any) => (
          <Item key={item.id} item={item} />
        ))}
      </section>
    </main>
  );
};

export default AboutMe;

p.s. Please also consider replacing all any hacks with specific type definitions, as using any defeats the purpose of using TypeScript in a project.

huangapple
  • 本文由 发表于 2023年5月24日 23:35:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76325222.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定