Skip to content

Scroll #75

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

Merged
merged 5 commits into from
Apr 12, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/scroll/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "use_scroll"
version = "0.1.0"
edition = "2021"

[dependencies]
dioxus-sdk = { workspace = true, features = ["scroll"] }
dioxus = { workspace = true }

[features]
web = ["dioxus/web"]
9 changes: 9 additions & 0 deletions examples/scroll/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# use_window_size

Learn how to use `use_root_scroll`.


### Run

**Web**
```dioxus serve --platform web```
Binary file added examples/scroll/public/favicon.ico
Binary file not shown.
35 changes: 35 additions & 0 deletions examples/scroll/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use dioxus::{
logger::tracing::{info, Level},
prelude::*,
};
use dioxus_sdk::utils::scroll::use_root_scroll;

fn main() {
dioxus::logger::init(Level::TRACE).unwrap();
launch(App);
}

#[component]
fn App() -> Element {
let random_text = "This is some random repeating text. ".repeat(1000);

let scroll_metrics = use_root_scroll();
use_effect(move || {
let scroll_metrics = scroll_metrics();
let distance_from_bottom = scroll_metrics.scroll_height
- (scroll_metrics.scroll_top + scroll_metrics.client_height);
info!("Distance from bottom: {}", distance_from_bottom);
let scroll_percentage = (scroll_metrics.scroll_top + scroll_metrics.client_height)
/ scroll_metrics.scroll_height;
info!("Scroll percentage: {}", scroll_percentage);
});

rsx! {
div { style: "text-align: center; padding: 20px; font-family: sans-serif;",
h1 { "Random Text" }
div { style: "margin: 20px; padding: 15px; border: 1px solid #ccc; border-radius: 5px;",
p { "{random_text}" }
}
}
}
}
3 changes: 3 additions & 0 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ system_theme = [
"dep:wasm-bindgen",
"dep:wasm-bindgen-futures",
]
scroll = [
"dep:serde",
]
window_size = [
# Shared
"dep:futures-util",
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ cfg_if::cfg_if! {
}

cfg_if::cfg_if! {
if #[cfg(any(feature = "channel", feature = "window_size", feature = "timing"))] {
if #[cfg(any(feature = "channel", feature = "scroll", feature = "window_size", feature = "timing"))] {
pub mod utils;
}
}
Expand Down
6 changes: 6 additions & 0 deletions sdk/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ cfg_if::cfg_if! {
}
}

cfg_if::cfg_if! {
if #[cfg(feature = "scroll")] {
pub mod scroll;
}
}

cfg_if::cfg_if! {
if #[cfg(feature = "timing")] {
pub mod timing;
Expand Down
132 changes: 132 additions & 0 deletions sdk/src/utils/scroll.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use dioxus::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};

/// Scroll metrics.
#[derive(serde::Deserialize, Clone, Debug)]
pub struct ScrollMetrics {
/// Current scroll position from top: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop
pub scroll_top: f64,
/// Current scroll position from left: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
pub scroll_left: f64,

/// Viewport height: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
pub client_height: f64,
/// Viewport width: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth
pub client_width: f64,

/// Content height: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
pub scroll_height: f64,
/// Content width: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth
pub scroll_width: f64,
}

// Static counter to generate unique IDs for each scroll tracker instance
static SCROLL_TRACKER_COUNTER: AtomicUsize = AtomicUsize::new(0);

/// Creates a signal that tracks root scrolling.
/// ```rust
/// use dioxus::{logger::tracing::{info, Level}, prelude::*};
/// use dioxus_sdk::utils::scroll::use_root_scroll;
///
/// fn main() {
/// dioxus::logger::init(Level::TRACE).unwrap();
/// launch(App);
/// }
///
/// #[component]
/// fn App() -> Element {
/// let random_text = "This is some random repeating text. ".repeat(1000);
///
/// let scroll_metrics = use_root_scroll();
/// use_effect(move || {
/// let scroll_metrics = scroll_metrics();
/// let distance_from_bottom = scroll_metrics.scroll_height - (scroll_metrics.scroll_top + scroll_metrics.client_height);
/// info!("Distance from bottom: {}", distance_from_bottom);
/// let scroll_percentage = (scroll_metrics.scroll_top + scroll_metrics.client_height) / scroll_metrics.scroll_height;
/// info!("Scroll percentage: {}", scroll_percentage);
/// });
///
/// rsx! {
/// p { "{random_text}" }
/// }
/// }
/// ```
pub fn use_root_scroll() -> Signal<ScrollMetrics> {
let callback_name = use_hook(|| {
let instance_id = SCROLL_TRACKER_COUNTER.fetch_add(1, Ordering::SeqCst);
format!("scrollCallback_{}", instance_id)
});

let mut scroll_metrics = use_signal(|| ScrollMetrics {
scroll_top: 0.0,
scroll_left: 0.0,
client_height: 0.0,
client_width: 0.0,
scroll_height: 0.0,
scroll_width: 0.0,
});

let future_callback_name = callback_name.clone();
use_future(move || {
let future_callback_name = future_callback_name.clone();
async move {
let callback_name = future_callback_name;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion to simplify this part:

Suggested change
let future_callback_name = callback_name.clone();
use_future(move || {
let future_callback_name = future_callback_name.clone();
async move {
let callback_name = future_callback_name;
use_future({
to_owned![callback_name];
move || {
to_owned![callback_name];
async move {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. PR seems to be stuck in "processing updates" for the past 10 min though.

Copy link
Contributor Author

@mcmah309 mcmah309 Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 hours later and still processing.. weird, had to amend and force push the same commit, but it is showing now.

let js_code = format!(
r#"
function {callback_name}() {{
const doc = document.documentElement;
const scrollTop = window.scrollY || doc.scrollTop;
const scrollLeft = window.scrollX || doc.scrollLeft;
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const contentHeight = doc.scrollHeight;
const contentWidth = doc.scrollWidth;

dioxus.send({{
scroll_top: scrollTop,
scroll_left: scrollLeft,
client_height: viewportHeight,
client_width: viewportWidth,
scroll_height: contentHeight,
scroll_width: contentWidth,
}});
}}

{callback_name}();

window['{callback_name}'] = {callback_name};
window.addEventListener('scroll', window['{callback_name}']);
window.addEventListener('resize', window['{callback_name}']);
"#,
);

let mut eval = document::eval(&js_code);

loop {
match eval.recv::<ScrollMetrics>().await {
Ok(metrics) => {
dioxus::logger::tracing::trace!("Got scroll metrics {:?}", metrics);
scroll_metrics.set(metrics);
}
Err(error) => dioxus::logger::tracing::error!(
"Error receiving scroll metrics: {:?}",
error
),
}
}
}
});

use_drop(move || {
let cleanup_code = format!(
r#"
window.removeEventListener('scroll', window['{callback_name}']);
window.removeEventListener('resize', window['{callback_name}']);
delete window['{callback_name}'];
"#,
);
let _ = document::eval(&cleanup_code);
});

scroll_metrics
}
Loading