Reactで一番下までスクロール


ちょっとしたことですが、以下のようなスクロールでちょっと詰まったので覚書程度に。。

やりたいこと

コンポーネント内でボタンを押す

アコーディオンが開く

指定位置までスクロールする

スクロールなかったら下にちょろっと出てくるだけなのであんまりよろしく無いかな〜と思いやってみました。

アコーディオン開くまで

App.js

const App = () => {
  const [isOpen, setIsOpen] = useState(false);

  const changeIsOpen = () => {
    setIsOpen(!isOpen);
  };

  return (
    <div className="App">
      <div className="wrapper">
        <button onClick={changeIsOpen}>Click Me!</button>
        <ul id="target" className={isOpen ? "ul open" : "ul close"}>
          <li>list1</li>
          <li>list2</li>
        </ul>
      </div>
    </div>
  );
};

export default App;

(styled-component使ってましたがとりあえず参考に…)

style.css
.ul {
  margin: auto;
  list-style: none;
  max-height: 0;
  overflow: hidden;
}

.open {
  max-height: 500px;
  transition: max-height 0.5s;
}

.close {
  max-height: 0;
  transition: max-height 0.5s;
}

試したこと1


const App = () => {
  const [isOpen, setIsOpen] = useState(false);

  const changeIsOpen = () => {
    setIsOpen(!isOpen);
    const target = document.getElementById("target");
    target.scrollIntoView({ behavior: "smooth", block: "end" });
  };

  return (
    <div className="App">
      <div className="wrapper">
        <button onClick={changeIsOpen}>Click Me!</button>
        <ul id="target" className={isOpen ? "ul open" : "ul close"}>
          <li>list1</li>
          <li>list2</li>
        </ul>
      </div>
    </div>
  );
};

export default App;

onClickの時に一緒に処理しようと思ったけどisOpenが変わる前に処理が走ってしまうので上手くいかなかった。

試したこと2

そりゃそうか…と思いながらuseEffectを使いましたがまだちゃんと動かない…


const App = () => {
  const [isOpen, setIsOpen] = useState(false);

  const ScrollBottom = target => {
    if (target) {
      target.scrollIntoView({ behavior: "smooth", block: "end" });
    }
  };

  const changeIsOpen = () => {
    setIsOpen(!isOpen);
  };

  useEffect(() => {
     const target = document.getElementById("target");
     if (target) {
         ScrollBottom(target)
     }
   }, [isOpen]);

  return (
    <div className="App">
      <div className="wrapper">
        <button onClick={changeIsOpen}>Click Me!</button>
        <ul id="target" className={isOpen ? "ul open" : "ul close"}>
          <li>list1</li>
          <li>list2</li>
        </ul>
      </div>
    </div>
  );
};

export default App;

原因はCSSのtransitionでした。
transitionで0.5s指定しているので、アコーディオンが開く前にjs動いていたようです。

解決

アコーディオンの要素にaddEventListenertransitionendをとってスクロールを動かしました。あとイベントが残っていくのでreturnでremoveEventListenerも。


  useEffect(() => {
    const target = document.getElementById("target");
    if (target) {
      target.addEventListener("transitionend", () => {
        ScrollBottom(target);
      });
      return () => {
        target.removeEventListener("transitionend", () => {
          ScrollBottom(target);
        });
      };
    }
  }, [isOpen]);

サンプル
https://codesandbox.io/s/transition-l0o2z?file=/src/App.js