Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: element.current.scrollIntoView() not working as expected in chrome. #23396

Open
karthik2265 opened this issue Mar 2, 2022 · 22 comments
Open
Labels
Component: DOM Resolution: Needs More Information Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug

Comments

@karthik2265
Copy link

Hey πŸ™‹β€β™‚οΈ

I have built a simple chatbot using react, and when the user sends a message i want to show the last message in the chatWindow component, so I used the following code:

useEffect(
    function () {
      if (lastmessageRef.current !== null) {
        lastmessageRef.current.scrollIntoView()
      }
    },
    [lastmessageRef]
  )

It works as expected in edge and Firefox, but on chrome it is behaving weird
llink-to-the-chatbot: https://karthik2265.github.io/worlds-best-chatbot/
github-repo-link: https://github.com/karthik2265/worlds-best-chatbot

Thank you

@karthik2265 karthik2265 added the Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug label Mar 2, 2022
@eps1lon
Copy link
Collaborator

eps1lon commented Mar 2, 2022

Note that lastmessageRef as a dependency won't do anything and can be removed. The effect won't fire if lastmessageRef.current changes.

Could you include a video of the behavior in Firefox and Chrome and explain with words what's "weird" about this behavior?
Also please include the exact version of those browsers and the operating system you're using.

Bonus: Could you convert the repro into a codesandbox.io? Makes it easier to debug the issue.

@noviceGuru
Copy link

noviceGuru commented Sep 16, 2022

I'm having the same problem.
scrollIntoView api doesn't work inside useEffect, while it fires by onClick.
Worth mentioning that it works in Firefox.
Here is the code:

import React, { useEffect, useRef, useState } from 'react'
import FirstPage from './components/FirstPage'
import SecondPage from './components/SecondPage'
import ThirdPage from './components/ThirdPage'

import './App.css'

const App = () => {
  const [atPage, setAtPage] = useState<number>(0)
  const refs = useRef<(null | HTMLDivElement)[]>([])
  const pages = [<FirstPage />, <SecondPage />, <ThirdPage />]

  const handleWheel = (event: any) => {
    console.log(event.deltaY > 0 ? 'down' : 'up')
    setAtPage(atPage => {
      let atPageCopied = JSON.parse(JSON.stringify(atPage))
      return event.deltaY < 0 ? (atPage > 0 ? atPageCopied - 1 : atPage) : (atPage < pages.length - 1 ? atPageCopied + 1 : atPage)
    })
  }
  const scrollItThere = (atPage: number) => refs.current[atPage]?.scrollIntoView({ block: 'start', behavior: 'smooth' })

  useEffect(() => {
    console.log(atPage)
    scrollItThere(atPage)
  }, [atPage])


  return (
    <div onWheel={handleWheel} >
      {pages.map((el, index) => <div
        key={index}
        ref={i => refs.current[index] = i}
        onClick={() => refs.current[0]?.scrollIntoView({ block: 'start', behavior: 'smooth' })}
      >
        {el}
      </div>)}
    </div>
  )
}

export default App

@vigneshvjay
Copy link

I'm having the same problem. scrollIntoView api doesn't work inside useEffect, while it fires by onClick. Worth mentioning that it works in Firefox. Here is the code:

import React, { useEffect, useRef, useState } from 'react'
import FirstPage from './components/FirstPage'
import SecondPage from './components/SecondPage'
import ThirdPage from './components/ThirdPage'

import './App.css'

const App = () => {
  const [atPage, setAtPage] = useState<number>(0)
  const refs = useRef<(null | HTMLDivElement)[]>([])
  const pages = [<FirstPage />, <SecondPage />, <ThirdPage />]

  const handleWheel = (event: any) => {
    console.log(event.deltaY > 0 ? 'down' : 'up')
    setAtPage(atPage => {
      let atPageCopied = JSON.parse(JSON.stringify(atPage))
      return event.deltaY < 0 ? (atPage > 0 ? atPageCopied - 1 : atPage) : (atPage < pages.length - 1 ? atPageCopied + 1 : atPage)
    })
  }
  const scrollItThere = (atPage: number) => refs.current[atPage]?.scrollIntoView({ block: 'start', behavior: 'smooth' })

  useEffect(() => {
    console.log(atPage)
    scrollItThere(atPage)
  }, [atPage])


  return (
    <div onWheel={handleWheel} >
      {pages.map((el, index) => <div
        key={index}
        ref={i => refs.current[index] = i}
        onClick={() => refs.current[0]?.scrollIntoView({ block: 'start', behavior: 'smooth' })}
      >
        {el}
      </div>)}
    </div>
  )
}

export default App

This works like a charm. Thanks buddy

@ValenW
Copy link

ValenW commented Jan 8, 2023

Come accross this issue with

React: 18.2.0
Chrome: Version 108.0.5359.125 (Official Build) (64-bit)
FireFox Developer Edition 109.0b9

, that element.current.scrollIntoView works in FF but not in Chrome, is there any progress?

@hwangyena
Copy link

I had the same problem
So I solved this by using setTimeout. (The flicker looks weird, but it works.)

// move to todo post
  useEffect(() => {
      ...
      const timer = setTimeout(() => {
      const todoPost = document.querySelector(`[data-post="${selectedTodoPost}"]`); 

      if (!todoPost) {
        return;
      }

      todoPost.scrollIntoView({ block: 'center' });
    }, 500);

    return () => clearTimeout(timer);
  }, [...]);

@jalexy12
Copy link

jalexy12 commented Feb 3, 2023

@hwangyena cool little thing I found out -- you can do what you did setTimeout(..., 0) and it works as well without delay, at least in my use case.

It's basically just a trick to defer until next tick in JS.

@eugenegodun
Copy link

eugenegodun commented Feb 9, 2023

I had the same problem and managed to fix it by using the scroll method for the parent node instead of scrollIntoView for the children node:

const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
        const scrollToTheBottom = () => {
            const scrollEl = ref.current;
            scrollEl?.scroll({
                top: scrollEl?.scrollHeight,
                behavior: 'smooth',
            });
        };
        scrollToTheBottom();
}, []);

return (
        <Wrap>
            <Scroll ref={ref}>
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
                <MessageGroup messages={messagesIncome} />
            </Scroll>
        </Wrap>
);

Works fine in Firefox, Chrome, Safari, Edge

scroll2.mp4

@danielo515
Copy link

The problem doesn't seem to be limited to lastmessageRef.current if you fetch the component by ID, it also does not work on chrome, but works fine in firefox.

@ajoslin103
Copy link

Not working in Electron 19 (Chromium)

This was working in React 16, I've since updated the project to React 18

@pjm4
Copy link

pjm4 commented Mar 6, 2023

For me it works if I remove the behavior: 'smooth', prop, however, I really want the smooth scrolling behaviour :(

@subhamayd2
Copy link

@pjm4 Yeah, that was the case for me as well.

This is what I did as a workaround:

const [shouldScrollToRow, setShouldScrollToRow] = useState(false);

    useEffect(() => {
        if (shouldScrollToRow) {
            setTimeout(() => {
                elemRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' });
            }, 100);
            setShouldScrollToRow(false);
        }
    }, [shouldScrollToRow]);

    useEffect(() => {
        // Listening for items getting added
        if (itemsUpdated) {
            setShouldScrollToRow(true);
            setItemsUpdated(false);
        }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [itemsUpdated]);

@Jerry-qiqi
Copy link

I want to know why this is happening

@jordanl17
Copy link

A workaround I've found it to use window.requestAnimationFrame:

useEffect(() => {
  if (itemsUpdated) {
    window.requestAnimationFrame(() => 
      elRef.current?.scrollIntoView()
    )
  }
}, [])

@mateussilva92
Copy link

mateussilva92 commented May 22, 2023

Just wanted to shed some light into this issue and why it can probably be closed.

This has nothing to do with react. This is a problem that is related to the chromium implementation of the scrollIntoView method.
There's been some open issues for a long time in the chromium project regarding this issue.

https://bugs.chromium.org/p/chromium/issues/detail?id=1121151
https://bugs.chromium.org/p/chromium/issues/detail?id=1043933
https://bugs.chromium.org/p/chromium/issues/detail?id=833617

So from what I've got from all the testing and searching the last few days is that chromium based browsers interrupt the scrollIntoView when any other scroll event is triggered.

This only happens with the scrollIntoView method, the scrollTo and scroll methods don't suffer from this issue.
You can see and test this by yourself on the following fiddle.

https://jsfiddle.net/2bnspw8e/8/

So then what's a solution if you want to keep the simplicity of scrollIntoView and have other scroll happen at the same time?

There is a ponyfill version of this method which reimplements it using the non broken scroll method.

I hope this clarifies what's actually happening behind the scene and provides a solution while we all wait for a chromium fix.

@ajoslin103
Copy link

Thanks for the digging and, I always wanted a one-trick pony!

This shows up at just the right time!

Cheers!

yen-tt added a commit to yext/chat-ui-react that referenced this issue Jun 6, 2023
- switch from icon libraries to using custom icon SVGs- add's autofocus prop for ChatInput
- fix scroll behavior in ChatPanel: (scrollIntoView issue with Chrome, but works in other browser like safari) facebook/react#23396
- switch from `yext/react-components`'s Markdown component to our own Markdown component since they will be removing that soon and `yext/react-components` also contains other packages/licenses that we don't want (e.g. mapbox)
- Generate third party license file for the library

J=CLIP-192
TEST=manual&auto

see that scroll behavior works as expected in Chrome and Safari
see that autofocus for ChatInput is off by default (rendering Panel way down in a page and see that it doesn't jump to it on load)
see that markdown is handled properly
see the icons are rendered as expected
@bn1ck
Copy link

bn1ck commented Sep 28, 2023

I ended up creating my own helper function.

export const smoothScroll = (targetY: number, duration: number) => {
  const startingY = window.pageYOffset;
  const diff = targetY - startingY;
  let start: number;

  window.requestAnimationFrame(function step(timestamp) {
    if (!start) start = timestamp;
    const time = timestamp - start;
    const percent = Math.min(time / duration, 1);

    window.scrollTo(0, startingY + diff * percent);

    if (time < duration) {
      window.requestAnimationFrame(step);
    }
  });
}

To call the function i use the following

if (viewerRef.current) {
      const top = viewerRef.current.offsetTop;
      smoothScroll(top, 500);
}

@iuriikomarov
Copy link

iuriikomarov commented Dec 28, 2023

IMO almost all the answers above are not correct.

You are trying to invoke scrolling in Effect but it should be done in LayoutEffect.

Function inside effect executes in parallel to DOM update commits and in 99% it will be invoked faster. So normally real DOM (according to the vDOM built up with your render function) is not ready yet at that point of time. This is why "workarounds" using setTimeout work somehow. But using LayoutEffect is more accurate.

Copy link

This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Apr 10, 2024
@danielo515
Copy link

this is not stale, because it is not fixed

@github-actions github-actions bot removed the Resolution: Stale Automatically closed due to inactivity label Apr 11, 2024
@hypo-thesis
Copy link

This is not fixed for sure. behavior: 'smooth' does work on FF but not on Chrome. I have codes to reproduce if you wish.

@missalyss
Copy link

I want to add that the useRef, useEffect combo didn't work for me (no movement at all, because elementRef.current wasn't defined until after the effect had run). Instead, I updated to useCallback.

  const elementRef = useCallback((domEl) => {
    if (domEl !== null ) {
      domEl.scrollIntoView({
        behavior: 'smooth',
      });
    }
  }, []);

...

<div ref={elementRef} >...</div>

@alexpeev9
Copy link

Hello.

After checking different scenarios, I also concluded that there is an issue with the way Chrome behaves with scrollIntoView() when you set the behavior to smooth.

In my case, the scroll randomly stops at the start or middle of the scroll.
A temporary solution for me would be to disable the smooth scroll for Chrome browsers.

const behavior = navigator.userAgent.includes('Chrome') ? 'auto' : 'smooth'
ref.current?.scrollIntoView({ behavior: behavior, block: 'start' })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Component: DOM Resolution: Needs More Information Status: Unconfirmed A potential issue that we haven't yet confirmed as a bug
Projects
None yet
Development

No branches or pull requests