Infinite scroll
The on_intersect event tells when an element appears in or disappears from the visible part of the page. The event (IntersectionEvent) carries is_intersecting and the intersection_ratio.
To load more content as the user scrolls, put a "sentinel" element after the content and load the next page when it becomes visible:
use vertigo::{ bind, dom, render::render_list, AutoJsJson, AutoMap, Computed, IntersectionEvent, RequestBuilder, Resource, Value, }; #[derive(AutoJsJson, Clone, PartialEq)] pub struct Post { id: u32, title: String, } // A cached request for every page let pages = AutoMap::new(|_, page: &u32| { RequestBuilder::get(format!("/api/posts?page={page}")).lazy_cache(|status, body| { if status == 200 { Some(body.into::<Vec<Post>>()) } else { None } }) }); let page_count = Value::new(1); let page_numbers = page_count.map(|count| (1..=count).collect::<Vec<u32>>()); // Every page is rendered once, when it's added to the list let list = render_list( &page_numbers, |page| *page, bind!(pages, |page| page.render_value(bind!(pages, |page| { pages.get(&page).render(|posts| { dom! { <div>{..posts.iter().map(|post| dom! { <p>{&post.title}</p> })}</div> } }) }))), ); // Number of the last page, once it's loaded and not empty let last_loaded = Computed::from(bind!(pages, page_count, |ctx| { let count = page_count.get(ctx); match pages.get(&count).get(ctx) { Resource::Ready(posts) if !posts.is_empty() => Some(count), _ => None, } })); // A new sentinel for every loaded page, so it reports itself also when it's still visible let sentinel = last_loaded.render_value_option(bind!(page_count, |last_loaded| { let count = last_loaded?; let load_more = bind!(page_count, |event: IntersectionEvent| { if event.is_intersecting { page_count.set(count + 1); } }); Some(dom! { <p on_intersect={load_more}>"Loading more..."</p> }) })); dom! { <div> {list} {sentinel} </div> }
The event fires when the element is mounted and later only when its visibility changes. So if the sentinel stayed visible after loading a page (because the page was short), it wouldn't report again. That's why a new sentinel is rendered after every loaded page. And as there's no sentinel while a page is loading, the next page is never requested too early.
Loading only when visible
The same event can postpone loading or rendering of anything until the user actually gets there:
use vertigo::{bind, dom, AutoJsJson, IntersectionEvent, RequestBuilder, Value}; #[derive(AutoJsJson, Clone, PartialEq)] pub struct Comment { author: String, text: String, } let visible = Value::new(false); let on_intersect = bind!(visible, |event: IntersectionEvent| { if event.is_intersecting { visible.set(true); } }); // Comments are fetched only when the user scrolls to them let comments = visible.render_value(|visible| { if !visible { return dom! { <p>"Comments"</p> }; } RequestBuilder::get("/api/comments") .lazy_cache(|status, body| { if status == 200 { Some(body.into::<Vec<Comment>>()) } else { None } }) .render(|comments| { dom! { <div> {..comments.iter().map(|comment| dom! { <p><b>{&comment.author}</b> ": " {&comment.text}</p> })} </div> } }) }); dom! { <section {on_intersect}>{comments}</section> }