简体   繁体   中英

Server side rendered element should be scrolled to bottom on initial render

I'm building a React server side rendered application (Next.js) and have a scrollable <div> that I want to be scrolled to the bottom on initial load of the page before React has hydrated the component.

That means a useLayoutEffect() hook like the following is not sufficient because there will be a brief flash of the element scrolled to the top before React mounts and runs this hook.

useLayoutEffect(() => {
  const element = ref.current;
  element.scrollTop = element.scrollHeight - element.clientHeight;
});

The approach that I think will work to prevent this is adding a <script> tag in the server side rendered page. For example:

function MyComponent() {
  return (
    <>
      <div style={{overflowY: "scroll"}}>
        ...
      </div>
      <script>
        var element = document.currentScript.previousElementSibling;
        element.scrollTop = element.scrollHeight - element.clientHeight;
      </script>
    </>
  );
}

However, I've found that when the <script> executes the clientHeight of the <div> element is 0.

Presumably, I need to wait for the browser to finish loading the DOM so I added a DOMContentLoaded listener:

function MyComponent() {
  return (
    <>
      <div style={{overflowY: "scroll"}}>
        ...
      </div>
      <script>
        var element = document.currentScript.previousElementSibling;
        document.addEventListener("DOMContentLoaded", function() {
          element.scrollTop = element.scrollHeight - element.clientHeight;
        });
      </script>
    </>
  );
}

However, with this approach it appears the browser still does a paint with scrollTop set to 0 and then processes the DOMContentLoaded event handler.

How can I write a <script> here that executes the “scroll to bottom” behavior after DOM properties like element.clientHeight have loaded but before the first browser paint so the user doesn't see a flash of the wrong scroll position.

Is there a better approach than this?

This version actually works in production. It doesn't work in development because Next.js use's Webpack's style-loader in development which means styles don't load until the JavaScript code has loaded and the JavaScript code is loaded at the end of the document <body> .

So in production element.clientHeight will be correct (since the CSS was loaded in the <head> ) but in development element.clientHeight will be 0 (since the CSS will be loaded at the end of <body> ).

function MyComponent() {
  return (
    <>
      <div style={{overflowY: "scroll"}}>
        ...
      </div>
      <script>
        var element = document.currentScript.previousElementSibling;
        element.scrollTop = element.scrollHeight - element.clientHeight;
      </script>
    </>
  );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM