Trigger Dash Callback from scroll (overflow div)

hello, Im trying to achieve some kind of virtualization for my div ‘table’: It has overflow-y-auto and I want to load divs on scroll as there are alot of them.
Currently I have a javascript file in assets with:
I need to get the ScrollTop value somehow, I just achieved it using javascript.

const checkElement = async selector => {
    while ( document.querySelector(selector) === null) {
        await new Promise( resolve =>  requestAnimationFrame(resolve) )
    }
    return document.querySelector(selector);
};

checkElement('#table').then((selector) => {
  console.log(selector);
  var obj = document.getElementById('scrollpos');
  selector.addEventListener('scroll', function() {
    obj.innerHTML = selector.scrollTop
    console.log('Current scroll value: ', selector.scrollTop);
    });
});

It works fine in displaying the current scrollposition in the div. However it does not trigger a Dash Callback on ‘scrollpos’
I found in multiple threads:

In general, using custom JS to interact with the DOM is not going to play well with Dash apps, as in the frontend, the app is a React app, which has it’s own virtual DOM.

and additionally:

You can’t update dcc.Store from the clientside either, because your component state will be out of sync with the session storage that holds the data on the window object clientside…

However there must be something I can do to trigger a Dash Callback from the scrollTop property, because I really want this to work.
I would appreciate all ideas.
Another Idea was: I don’t set overflow-y-auto and put a vertical slider next to my div table that simulates the scrollbar somehow, but the Issue here is that you then cant scroll with your mouse inside the Div and that is cool I think. Nevertheless if theres no way to do it, I have to work with that solution I guess.
Thanks in advance.

Hello @Louis,

This is due to syncing between the DOM elements and react/dash.

What you could do is update a dcc.store based upon scroll position, then have a hidden button that you can click at the end of the event that causes the store to sync with itself, via a clientside callback.

Then the store updating triggers the div to load another element.

Thank you so much, it actually worked. Nowhere I found a solution to triggering a Dash Callback from Javascript.
Here is the full solution:

#add virtualization based on scroll
@app.callback(
    Output('table', 'n_clicks'),
    Input('scrollpos', 'children')
)
def virtualization_table(scroll):
    print("scroll:")
    print(scroll)
    return None

app.clientside_callback(
    """
    function(n) {
        var obj = document.getElementById('scrollpos');
        var scrollpos = obj.innerHTML;
        console.log('scrollpos: ' ,scrollpos)
        console.log('n_clicks:', n)
        return scrollpos;
    }
    """,
    Output('scrollpos', 'children'),
    Input('scroll_btn', 'n_clicks')
)

and in the js file:

const checkElement = async selector => {
    while ( document.querySelector(selector) === null) {
        await new Promise( resolve =>  requestAnimationFrame(resolve) )
    }
    return document.querySelector(selector);
};

checkElement('#table').then((selector) => {
  console.log(selector);
  var obj = document.getElementById('scrollpos');
  selector.addEventListener('scroll', function() {
    obj.innerHTML = selector.scrollTop
    console.log('Current scroll value: ', selector.scrollTop);
    
    document.getElementById('scroll_btn').click(); // trigger click event on hidden button
  });
});
2 Likes