1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
// animation/src/glow.rs
// Component-isolated glow effect using Dioxus hooks
use dioxus::prelude::*;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use wasm_bindgen::JsValue;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use web_sys::HtmlElement;
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
use crate::style::StyleBuilder;
/// Glow component properties
#[derive(Clone, Props, PartialEq)]
pub struct GlowProps {
#[props(default = "rgba(255, 255, 255, 0.3)".to_string())]
pub color: String,
#[props(default = 0.3)]
pub intensity: f64,
#[props(default)]
pub children: Element,
}
/// Glow component with mouse-following effect
///
/// This component provides a glow effect that follows the mouse cursor
/// within the component boundaries. It is fully component-isolated,
/// using Dioxus hooks for state management and lifecycle handling.
///
/// # Example
///
/// ```ignore
/// use dioxus::prelude::*;
/// use hikari_animation::Glow;
///
/// rsx! {
/// Glow {
/// color: "rgba(255, 255, 255, 0.3)",
/// intensity: 0.5,
/// Card {
/// "Card with glow effect"
/// }
/// }
/// }
/// ```
#[component]
pub fn Glow(props: GlowProps) -> Element {
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
{
use wasm_bindgen::JsCast;
// Clone props for use in event handlers
let color = props.color.clone();
let intensity = props.intensity.to_string();
// Clone for use in rsx! block (color and intensity are moved into onmousemove_handler)
let rsx_color = color.clone();
let rsx_intensity = intensity.clone();
// Mouse move handler - directly update CSS variables without re-render
let onmousemove_handler = move |event: Event<MouseData>| {
// Downcast to web_sys::MouseEvent to access DOM APIs
if let Some(web_event) = event.downcast::<web_sys::MouseEvent>() {
// Get the target element (may be a child of glow-wrapper)
if let Some(target) = web_event.target() {
if let Some(mut current) = target.dyn_into::<web_sys::Element>().ok() {
// Traverse up to find .hi-glow-wrapper element
loop {
let class_list = current.class_list();
if class_list.contains("hi-glow-wrapper") {
break;
}
// Move to parent
match current.parent_element() {
Some(parent) => current = parent,
None => return,
}
}
if let Some(html_element) = current.dyn_into::<HtmlElement>().ok() {
// Get element bounding box
let rect = html_element.get_bounding_client_rect();
// Get client coordinates from web event
let client_x = web_event.client_x() as f64;
let client_y = web_event.client_y() as f64;
// Calculate relative position as percentage
let relative_x = client_x - rect.left();
let relative_y = client_y - rect.top();
let width = rect.width();
let height = rect.height();
// Debug logging
let _ = web_sys::console::log_1(&format!(
"Glow: client=({:.0},{:.0}) rect=({:.0},{:.0},{:.0},{:.0}) size=({:.0},{:.0})",
client_x, client_y, rect.left(), rect.top(), rect.right(), rect.bottom(), width, height
).into());
// Handle zero or negative dimensions
if width <= 0.0 || height <= 0.0 {
let _ = web_sys::console::log_1(&JsValue::from(
"Glow: Invalid dimensions, using 50%",
));
StyleBuilder::new(&html_element)
.add_custom("--glow-x", "50%")
.add_custom("--glow-y", "50%")
.add_custom("--glow-color", &color)
.add_custom("--glow-intensity", &intensity.to_string())
.apply();
return;
}
let percent_x = if width > 0.0 {
((relative_x / width) * 100.0).clamp(0.0, 100.0)
} else {
50.0
};
let percent_y = if height > 0.0 {
((relative_y / height) * 100.0).clamp(0.0, 100.0)
} else {
50.0
};
let _ = web_sys::console::log_1(
&format!("Glow: percent=({:.1}%,{:.1}%)", percent_x, percent_y)
.into(),
);
// Update CSS variables directly on DOM (no re-render)
let _ = web_sys::console::log_1(
&format!(
"Glow: --glow-x: {:.1}%; --glow-y: {:.1}%; --glow-color: {}; --glow-intensity: {};",
percent_x, percent_y, color, intensity
).into()
);
StyleBuilder::new(&html_element)
.add_custom("--glow-x", &format!("{:.1}%", percent_x))
.add_custom("--glow-y", &format!("{:.1}%", percent_y))
.add_custom("--glow-color", &color)
.add_custom("--glow-intensity", &intensity.to_string())
.apply();
}
}
}
}
};
rsx! {
div {
class: "hi-glow-wrapper",
// Initial style
style: "--glow-x: 50%; --glow-y: 50%; --glow-color: {rsx_color}; --glow-intensity: {rsx_intensity};",
onmousemove: onmousemove_handler,
{ props.children }
}
}
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
{
// SSR fallback: just render children
rsx! {
div { class: "hi-glow-wrapper", { props.children } }
}
}
}