Skip to content

Commit a7b261e

Browse files
authored
Scroll (#75)
* Add use_root_scroll * Add scroll example * Move use_root_scroll callback name calculation to hook * fix typos * nit use_root_scroll
1 parent 1c6e6a8 commit a7b261e

File tree

8 files changed

+198
-1
lines changed

8 files changed

+198
-1
lines changed

examples/scroll/Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "use_scroll"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
dioxus-sdk = { workspace = true, features = ["scroll"] }
8+
dioxus = { workspace = true }
9+
10+
[features]
11+
web = ["dioxus/web"]

examples/scroll/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# use_scroll
2+
3+
Learn how to use `use_root_scroll`.
4+
5+
6+
### Run
7+
8+
**Web**
9+
```dioxus serve --platform web```

examples/scroll/public/favicon.ico

130 KB
Binary file not shown.

examples/scroll/src/main.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use dioxus::{
2+
logger::tracing::{info, Level},
3+
prelude::*,
4+
};
5+
use dioxus_sdk::utils::scroll::use_root_scroll;
6+
7+
fn main() {
8+
dioxus::logger::init(Level::TRACE).unwrap();
9+
launch(App);
10+
}
11+
12+
#[component]
13+
fn App() -> Element {
14+
let random_text = "This is some random repeating text. ".repeat(1000);
15+
16+
let scroll_metrics = use_root_scroll();
17+
use_effect(move || {
18+
let scroll_metrics = scroll_metrics();
19+
let distance_from_bottom = scroll_metrics.scroll_height
20+
- (scroll_metrics.scroll_top + scroll_metrics.client_height);
21+
info!("Distance from bottom: {}", distance_from_bottom);
22+
let scroll_percentage = (scroll_metrics.scroll_top + scroll_metrics.client_height)
23+
/ scroll_metrics.scroll_height;
24+
info!("Scroll percentage: {}", scroll_percentage);
25+
});
26+
27+
rsx! {
28+
div { style: "text-align: center; padding: 20px; font-family: sans-serif;",
29+
h1 { "Random Text" }
30+
div { style: "margin: 20px; padding: 15px; border: 1px solid #ccc; border-radius: 5px;",
31+
p { "{random_text}" }
32+
}
33+
}
34+
}
35+
}

sdk/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ system_theme = [
4848
"dep:wasm-bindgen",
4949
"dep:wasm-bindgen-futures",
5050
]
51+
scroll = [
52+
"dep:serde",
53+
]
5154
window_size = [
5255
# Shared
5356
"dep:futures-util",

sdk/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ cfg_if::cfg_if! {
1313
}
1414

1515
cfg_if::cfg_if! {
16-
if #[cfg(any(feature = "channel", feature = "window_size", feature = "timing"))] {
16+
if #[cfg(any(feature = "channel", feature = "scroll", feature = "window_size", feature = "timing"))] {
1717
pub mod utils;
1818
}
1919
}

sdk/src/utils/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ cfg_if::cfg_if! {
1212
}
1313
}
1414

15+
cfg_if::cfg_if! {
16+
if #[cfg(feature = "scroll")] {
17+
pub mod scroll;
18+
}
19+
}
20+
1521
cfg_if::cfg_if! {
1622
if #[cfg(feature = "timing")] {
1723
pub mod timing;

sdk/src/utils/scroll.rs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use dioxus::prelude::*;
2+
use std::sync::atomic::{AtomicUsize, Ordering};
3+
4+
/// Scroll metrics.
5+
#[derive(serde::Deserialize, Clone, Debug)]
6+
pub struct ScrollMetrics {
7+
/// Current scroll position from top: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop
8+
pub scroll_top: f64,
9+
/// Current scroll position from left: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollLeft
10+
pub scroll_left: f64,
11+
12+
/// Viewport height: https://developer.mozilla.org/en-US/docs/Web/API/Element/clientHeight
13+
pub client_height: f64,
14+
/// Viewport width: https://developer.mozilla.org/en-US/docs/Web/API/Element/clientWidth
15+
pub client_width: f64,
16+
17+
/// Content height: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight
18+
pub scroll_height: f64,
19+
/// Content width: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollWidth
20+
pub scroll_width: f64,
21+
}
22+
23+
// Static counter to generate unique IDs for each scroll tracker instance
24+
static SCROLL_TRACKER_COUNTER: AtomicUsize = AtomicUsize::new(0);
25+
26+
/// Creates a signal that tracks root scrolling.
27+
/// ```rust
28+
/// use dioxus::{logger::tracing::{info, Level}, prelude::*};
29+
/// use dioxus_sdk::utils::scroll::use_root_scroll;
30+
///
31+
/// fn main() {
32+
/// dioxus::logger::init(Level::TRACE).unwrap();
33+
/// launch(App);
34+
/// }
35+
///
36+
/// #[component]
37+
/// fn App() -> Element {
38+
/// let random_text = "This is some random repeating text. ".repeat(1000);
39+
///
40+
/// let scroll_metrics = use_root_scroll();
41+
/// use_effect(move || {
42+
/// let scroll_metrics = scroll_metrics();
43+
/// let distance_from_bottom = scroll_metrics.scroll_height - (scroll_metrics.scroll_top + scroll_metrics.client_height);
44+
/// info!("Distance from bottom: {}", distance_from_bottom);
45+
/// let scroll_percentage = (scroll_metrics.scroll_top + scroll_metrics.client_height) / scroll_metrics.scroll_height;
46+
/// info!("Scroll percentage: {}", scroll_percentage);
47+
/// });
48+
///
49+
/// rsx! {
50+
/// p { "{random_text}" }
51+
/// }
52+
/// }
53+
/// ```
54+
pub fn use_root_scroll() -> Signal<ScrollMetrics> {
55+
let callback_name = use_hook(|| {
56+
let instance_id = SCROLL_TRACKER_COUNTER.fetch_add(1, Ordering::SeqCst);
57+
format!("scrollCallback_{}", instance_id)
58+
});
59+
60+
let mut scroll_metrics = use_signal(|| ScrollMetrics {
61+
scroll_top: 0.0,
62+
scroll_left: 0.0,
63+
client_height: 0.0,
64+
client_width: 0.0,
65+
scroll_height: 0.0,
66+
scroll_width: 0.0,
67+
});
68+
69+
use_future({
70+
to_owned![callback_name];
71+
move || {
72+
to_owned![callback_name];
73+
async move {
74+
let js_code = format!(
75+
r#"
76+
function {callback_name}() {{
77+
const doc = document.documentElement;
78+
const scrollTop = window.scrollY || doc.scrollTop;
79+
const scrollLeft = window.scrollX || doc.scrollLeft;
80+
const viewportHeight = window.innerHeight;
81+
const viewportWidth = window.innerWidth;
82+
const contentHeight = doc.scrollHeight;
83+
const contentWidth = doc.scrollWidth;
84+
85+
dioxus.send({{
86+
scroll_top: scrollTop,
87+
scroll_left: scrollLeft,
88+
client_height: viewportHeight,
89+
client_width: viewportWidth,
90+
scroll_height: contentHeight,
91+
scroll_width: contentWidth,
92+
}});
93+
}}
94+
95+
{callback_name}();
96+
97+
window['{callback_name}'] = {callback_name};
98+
window.addEventListener('scroll', window['{callback_name}']);
99+
window.addEventListener('resize', window['{callback_name}']);
100+
"#,
101+
);
102+
103+
let mut eval = document::eval(&js_code);
104+
105+
loop {
106+
match eval.recv::<ScrollMetrics>().await {
107+
Ok(metrics) => {
108+
dioxus::logger::tracing::trace!("Got scroll metrics {:?}", metrics);
109+
scroll_metrics.set(metrics);
110+
}
111+
Err(error) => dioxus::logger::tracing::error!(
112+
"Error receiving scroll metrics: {:?}",
113+
error
114+
),
115+
}
116+
}
117+
}
118+
}
119+
});
120+
121+
use_drop(move || {
122+
let cleanup_code = format!(
123+
r#"
124+
window.removeEventListener('scroll', window['{callback_name}']);
125+
window.removeEventListener('resize', window['{callback_name}']);
126+
delete window['{callback_name}'];
127+
"#,
128+
);
129+
let _ = document::eval(&cleanup_code);
130+
});
131+
132+
scroll_metrics
133+
}

0 commit comments

Comments
 (0)