NO WAY to set focus at top of screen on page reload. Really?

My latest failed attempt below (4 days since I asked the original question).

Imagine scrolling to the bottom of a long chart set on your phone and tap ā€œLoad Next Datasetā€ . Now you have to manually scroll all the way to the top to see the new data in order. Now imagine you need to do this 60 times in a rowā€¦

The following doesnā€™t work in Streamlit even though the script is firing and the console logs print both times.

Note: After reading more of the manual it appears that components.html encapsulates what it can affect to itself so there is no way the following code could affect the rest of the page.

def market_scan(exchange):
    components.html(
        """
        <script>
            console.log("Will it run this?")

            if (history.scrollRestoration) {
                history.scrollRestoration = 'manual';
            } else {
                window.onbeforeunload = function () {
                    window.scrollTo(0, 0);
                }
            }
            
            console.log("It ran but completely ingnores the javascript in between")
        </script>
        """
    )
1 Like

Hi @bilbo,

What version of Streamlit are you using? An is this something that is only happening when youā€™re trying to access your app from a phone? Are you trying to optimize your app for use on a phone?

I ask because this post you made: Automatically Scroll to Top of Page on Data Refresh, that code works for me as you were describing. Iā€™m using the most up-to-date streamlit version 0.84.1

Can you copy some of the Plotly code that you said was giving you issues? You can also record your app from the hamburger menu (top right corner) and add it here to demo the behaviour that your seeing.

Happy Streamlit-ing!
Marisa

Thank your for replying @Marisa_Smith ā€¦You inspired me to try again

As for the previous postā€™s code. If you remove the ā€˜fakeā€™ delay the results vary a lot depending on machine load. This is obviously a bad solution to the problem:)

Streamlit version 85.0 (earlier had same problem)
Using on desktop with no optimization at all

I did get the following snippet to work (So Farā€¦) but it is very Kludgy and doesnā€™t inspire confidence.

NOTE: the placement of the Spinner and the time length are important or it wont work. See comments below)

I can upload the original and klugy fix videos if you still want to seeā€¦



# Display Each chart and header
def show_charts(coin, coin_period_dict):
    print_chart_header(coin, "1d")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_1d'], coin), use_container_width=True)                    # Plot 1 day chart  
    print_chart_header(coin, "16h")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_16h'], coin), use_container_width=True)                    # Plot 16h 
    print_chart_header(coin, "12h")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_12h'], coin), use_container_width=True)                      # Plot 12h
    print_chart_header(coin, "8h")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_8h'], coin), use_container_width=True)                    # Plot 8h 
    print_chart_header(coin, "4h")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_4h'], coin), use_container_width=True)                      # Plot 4h
    print_chart_header(coin, "2h")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_2h'], coin), use_container_width=True)                    # Plot 30m  
    print_chart_header(coin, "1h")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_1h'], coin), use_container_width=True)                      # Plot 15m
    print_chart_header(coin, "30m")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_30m'], coin), use_container_width=True)                    # Plot 30m                         
    print_chart_header(coin, "15m")
    st.plotly_chart(get_candlestick_figure(coin_period_dict['df_15m'], coin), use_container_width=True)                      # Plot 15m
    

# ------------------------ Begin Display Page Layout ----------------------------------------
components.html("<center><h1 style = color:white> Market Scan </h1></center>", height=70)
with st.form("hello"):
    coin = st.session_state.current_coin                    # Get name of current coin to print
    coin_period_dict = st.session_state.next_coin_data      # Get preloaded dictionary of coin data

    # with st.spinner("Loading..."):
    #     time.sleep(0.2)

    here = st.empty()

    with st.spinner("Loading..."):      # This must be HERE and not above 'here = ...' line above
        time.sleep(0.2)                 # Less that 0.2 fails

    with here.beta_container():
        show_charts(coin, coin_period_dict)                     # Display each chart in order

    st.form_submit_button("Next", on_click=test, args=("hello", "world"))
# ------------------------ End Display Page Layout ----------------------------------------

@bilbo,

could you also answer these:

I did a screen record using your basic example but replacing the line charts with Plotly ones. When I scroll down to press the next button, the app reloads and Iā€™m bumped to the top of the page where I scroll down again to see the new data.

Iā€™m attaching a screen recording here, is this the behaviour youā€™re looking for?

Happy Streamlit-ing!
Marisa

Thatā€™s exactly what I want Marisa. Force the page to set focus at the top when it reloads.

The solution I posted works (mostly) but is inconsistent depending on CPU load. It also adds an otherwise useless sleep cycle which is not very efficient.

With Javascript you can force the browser to the top on page reload. Is there an option currently for Streamlit to do this.

During my research I found a post where someone had the opposite problem. The Streamlit team must have forced the browser to hold its position on reload at some point. The option to choose one or the other would be good.

Hereā€™s a link to the undesired reload video (7MB)
https://drive.google.com/file/d/16ZOQsFFuLZSHrszK5n7P00GgsxOCVjr4/view?usp=sharing

Thank You Marisa!

Hey @bilbo,

Do you have a GitHub repo for this? I would like to look at the complete code.

I noticed that in your version you have a line that goes around all of the graphs and the button like in st.form. This makes me think that you put your graphs and the button in a form.

If you have if you take your code out of that form it should work as expected. If not I will need to see a repo to try and reproduce this in full.

Let me know!
Marisa

Hello @bilbo,

As you said, components.html encapsulates its code inside an iframe, but you can access the rest of the page with window.parent. That said, to scroll to the top, youā€™d do something like this:

<script>window.parent.document.querySelector('section.main').scrollTo(0, 0);</script>

Thereā€™s a little catch though. Passing this line directly to components.html may work only once, as it will render (and thus execute that javascript line) only once if no parameter was changed between two script runs.

If you manage your different pages with st.session_state, what you could do is change the width parameter to force it to re-render every run, like so:

components.html(
    "<script>window.parent.document.querySelector('section.main').scrollTo(0, 0);</script>",
    width=(st.session_state.page % 2), # This will alternate between 0 and 1 with no visual changes, even with many pages
    height=0,
)
2 Likes

Hello Marisa,

Iā€™m going to try okidā€™s solution below tomorrow, If that doesnā€™t work out I will upload the relevant files to github.

Thanks,
Jim

1 Like

Thanks @okld

Thatā€™s a good idea!

I appreciate the detailed solution. I will give it a try tomorrow.

1 Like

Sounds good to me!

Let us know if that works for ya!

@okld @Marisa_Smith

I believe this is what okid was suggesting but is does not work for me. Very simple script but focus stays at the bottom on button click though.

import streamlit as st
import streamlit.components.v1 as components

if "page" not in st.session_state:
    st.session_state.page = 0
if "counter" not in st.session_state:
    st.session_state.counter = 1

components.html(
    "<script>window.parent.document.querySelector('section.main').scrollTo(0, 0);</script>",
    width=(st.session_state.page % 2), # This will alternate between 0 and 1 with no visual changes, even with many pages
    height=0,
)

st.header("Set Focus Here on Page Reload")
st.write("Please click button at bottom of page.")
for x in range(20):
    text_field = st.write("Field "+str(x))

if st.button("Load New Page"):
    st.session_state.counter += 1

st.write(f"Page load: {st.session_state.counter}")

Make sure to use a variable that changes. In your example this is counter and not page. And call components.html after the part which updates your counter. Otherwise the script wonā€™t go up on the first rerun.

That said, changing the width might not be enough to re-render the component after all :confused: But updating the script itself seems to work better. A word of caution though: just make sure that this counter variable cannot be changed by the end user, otherwise one would be able to inject and execute javascript code in your app.

import streamlit as st
import streamlit.components.v1 as components

if "page" not in st.session_state:
    st.session_state.page = 0
if "counter" not in st.session_state:
    st.session_state.counter = 1

st.header("Set Focus Here on Page Reload")
st.write("Please click button at bottom of page.")
for x in range(20):
    text_field = st.write("Field "+str(x))

if st.button("Load New Page"):
    st.session_state.counter += 1

components.html(
    f"""
        <!--{st.session_state.counter}-->
        <script>
            window.parent.document.querySelector('section.main').scrollTo(0, 0);
        </script>
    """,
    height=0
)

st.write(f"Page load: {st.session_state.counter}")
1 Like

Thank you so much @okid

I got your latest working with one slight change. The page would not refocus at the top of the page (in Brave anyway) with the session state counter embedded in the comment, but, it works fine inside a couple of paragraph tags. This works fine since the height is set to zero and canā€™t been seen anyway.

This is a good solution.

Working code here:

import streamlit as st
import streamlit.components.v1 as components

if "counter" not in st.session_state:
    st.session_state.counter = 1

st.header("Set Focus Here on Page Reload")
st.write("Please click button at bottom of page.")
for x in range(20):
    text_field = st.write("Field "+str(x))

if st.button("Load New Page"):
    st.session_state.counter += 1

components.html(
    f"""
        <p>{st.session_state.counter}</p>
        <script>
            window.parent.document.querySelector('section.main').scrollTo(0, 0);
        </script>
    """,
    height=0
)

st.write(f"Page load: {st.session_state.counter}")
1 Like