@chemmangat/easy-scroll 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -0
- package/dist/index.d.mts +57 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +344 -0
- package/dist/index.mjs +312 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# react-scroll-motion
|
|
2
|
+
|
|
3
|
+
> Lightweight scroll-driven animations using native CSS Scroll Timeline API. Zero dependencies. Pure performance.
|
|
4
|
+
|
|
5
|
+
[](package.json)
|
|
6
|
+
[](tsconfig.json)
|
|
7
|
+
[](package.json)
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install react-scroll-motion
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { RevealOnScroll, ScrollProgress } from 'react-scroll-motion';
|
|
19
|
+
|
|
20
|
+
export default function Page() {
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<ScrollProgress color="#6366f1" height={3} />
|
|
24
|
+
|
|
25
|
+
<RevealOnScroll animation="fadeInUp" delay={200}>
|
|
26
|
+
<h1>Hello World</h1>
|
|
27
|
+
</RevealOnScroll>
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Components
|
|
34
|
+
|
|
35
|
+
### `<RevealOnScroll>`
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
<RevealOnScroll
|
|
39
|
+
animation="fadeInUp"
|
|
40
|
+
delay={200}
|
|
41
|
+
duration={600}
|
|
42
|
+
threshold={0.1}
|
|
43
|
+
once={true}
|
|
44
|
+
>
|
|
45
|
+
<YourContent />
|
|
46
|
+
</RevealOnScroll>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `<ScrollProgress>`
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
<ScrollProgress color="#6366f1" height={3} />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `<ParallaxSection>`
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<ParallaxSection speed={0.5}>
|
|
59
|
+
<YourContent />
|
|
60
|
+
</ParallaxSection>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `<CountOnScroll>`
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<CountOnScroll
|
|
67
|
+
from={0}
|
|
68
|
+
to={10000}
|
|
69
|
+
formatFn={(n) => n.toLocaleString()}
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `<StaggerChildren>`
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<StaggerChildren animation="scaleUp" staggerDelay={100}>
|
|
77
|
+
<Card />
|
|
78
|
+
<Card />
|
|
79
|
+
<Card />
|
|
80
|
+
</StaggerChildren>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Animation Types
|
|
84
|
+
|
|
85
|
+
- `fadeIn` - Fade in
|
|
86
|
+
- `fadeInUp` - Fade in + slide up
|
|
87
|
+
- `fadeInDown` - Fade in + slide down
|
|
88
|
+
- `slideInLeft` - Slide from left
|
|
89
|
+
- `slideInRight` - Slide from right
|
|
90
|
+
- `scaleUp` - Scale up
|
|
91
|
+
- `scaleDown` - Scale down
|
|
92
|
+
- `rotateIn` - Rotate in
|
|
93
|
+
- `blurIn` - Blur to clear
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
type AnimationType = 'fadeIn' | 'fadeInUp' | 'fadeInDown' | 'slideInLeft' | 'slideInRight' | 'scaleUp' | 'scaleDown' | 'rotateIn' | 'blurIn';
|
|
5
|
+
interface ScrollAnimationOptions {
|
|
6
|
+
delay?: number;
|
|
7
|
+
duration?: number;
|
|
8
|
+
threshold?: number;
|
|
9
|
+
once?: boolean;
|
|
10
|
+
easing?: string;
|
|
11
|
+
}
|
|
12
|
+
interface UseScrollAnimationReturn {
|
|
13
|
+
ref: React.RefObject<HTMLElement>;
|
|
14
|
+
isVisible: boolean;
|
|
15
|
+
progress: number;
|
|
16
|
+
}
|
|
17
|
+
declare function useScrollAnimation(animation: AnimationType, options?: ScrollAnimationOptions): UseScrollAnimationReturn;
|
|
18
|
+
|
|
19
|
+
interface RevealOnScrollProps extends ScrollAnimationOptions {
|
|
20
|
+
animation: AnimationType;
|
|
21
|
+
children: React$1.ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
declare function RevealOnScroll({ animation, children, className, delay, duration, threshold, once, easing, }: RevealOnScrollProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
|
|
26
|
+
interface ScrollProgressProps {
|
|
27
|
+
color?: string;
|
|
28
|
+
height?: number;
|
|
29
|
+
zIndex?: number;
|
|
30
|
+
}
|
|
31
|
+
declare function ScrollProgress({ color, height, zIndex, }: ScrollProgressProps): react_jsx_runtime.JSX.Element;
|
|
32
|
+
|
|
33
|
+
interface ParallaxSectionProps {
|
|
34
|
+
speed?: number;
|
|
35
|
+
children: React$1.ReactNode;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
declare function ParallaxSection({ speed, children, className, }: ParallaxSectionProps): react_jsx_runtime.JSX.Element;
|
|
39
|
+
|
|
40
|
+
interface CountOnScrollProps {
|
|
41
|
+
from: number;
|
|
42
|
+
to: number;
|
|
43
|
+
duration?: number;
|
|
44
|
+
formatFn?: (value: number) => string;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
declare function CountOnScroll({ from, to, duration, formatFn, className, }: CountOnScrollProps): react_jsx_runtime.JSX.Element;
|
|
48
|
+
|
|
49
|
+
interface StaggerChildrenProps extends Omit<ScrollAnimationOptions, 'delay'> {
|
|
50
|
+
animation: AnimationType;
|
|
51
|
+
staggerDelay?: number;
|
|
52
|
+
children: React$1.ReactNode;
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
declare function StaggerChildren({ animation, staggerDelay, children, className, duration, threshold, once, easing, }: StaggerChildrenProps): react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { type AnimationType, CountOnScroll, ParallaxSection, RevealOnScroll, type ScrollAnimationOptions, ScrollProgress, StaggerChildren, useScrollAnimation };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
type AnimationType = 'fadeIn' | 'fadeInUp' | 'fadeInDown' | 'slideInLeft' | 'slideInRight' | 'scaleUp' | 'scaleDown' | 'rotateIn' | 'blurIn';
|
|
5
|
+
interface ScrollAnimationOptions {
|
|
6
|
+
delay?: number;
|
|
7
|
+
duration?: number;
|
|
8
|
+
threshold?: number;
|
|
9
|
+
once?: boolean;
|
|
10
|
+
easing?: string;
|
|
11
|
+
}
|
|
12
|
+
interface UseScrollAnimationReturn {
|
|
13
|
+
ref: React.RefObject<HTMLElement>;
|
|
14
|
+
isVisible: boolean;
|
|
15
|
+
progress: number;
|
|
16
|
+
}
|
|
17
|
+
declare function useScrollAnimation(animation: AnimationType, options?: ScrollAnimationOptions): UseScrollAnimationReturn;
|
|
18
|
+
|
|
19
|
+
interface RevealOnScrollProps extends ScrollAnimationOptions {
|
|
20
|
+
animation: AnimationType;
|
|
21
|
+
children: React$1.ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
declare function RevealOnScroll({ animation, children, className, delay, duration, threshold, once, easing, }: RevealOnScrollProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
|
|
26
|
+
interface ScrollProgressProps {
|
|
27
|
+
color?: string;
|
|
28
|
+
height?: number;
|
|
29
|
+
zIndex?: number;
|
|
30
|
+
}
|
|
31
|
+
declare function ScrollProgress({ color, height, zIndex, }: ScrollProgressProps): react_jsx_runtime.JSX.Element;
|
|
32
|
+
|
|
33
|
+
interface ParallaxSectionProps {
|
|
34
|
+
speed?: number;
|
|
35
|
+
children: React$1.ReactNode;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
declare function ParallaxSection({ speed, children, className, }: ParallaxSectionProps): react_jsx_runtime.JSX.Element;
|
|
39
|
+
|
|
40
|
+
interface CountOnScrollProps {
|
|
41
|
+
from: number;
|
|
42
|
+
to: number;
|
|
43
|
+
duration?: number;
|
|
44
|
+
formatFn?: (value: number) => string;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
declare function CountOnScroll({ from, to, duration, formatFn, className, }: CountOnScrollProps): react_jsx_runtime.JSX.Element;
|
|
48
|
+
|
|
49
|
+
interface StaggerChildrenProps extends Omit<ScrollAnimationOptions, 'delay'> {
|
|
50
|
+
animation: AnimationType;
|
|
51
|
+
staggerDelay?: number;
|
|
52
|
+
children: React$1.ReactNode;
|
|
53
|
+
className?: string;
|
|
54
|
+
}
|
|
55
|
+
declare function StaggerChildren({ animation, staggerDelay, children, className, duration, threshold, once, easing, }: StaggerChildrenProps): react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { type AnimationType, CountOnScroll, ParallaxSection, RevealOnScroll, type ScrollAnimationOptions, ScrollProgress, StaggerChildren, useScrollAnimation };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CountOnScroll: () => CountOnScroll,
|
|
24
|
+
ParallaxSection: () => ParallaxSection,
|
|
25
|
+
RevealOnScroll: () => RevealOnScroll,
|
|
26
|
+
ScrollProgress: () => ScrollProgress,
|
|
27
|
+
StaggerChildren: () => StaggerChildren,
|
|
28
|
+
useScrollAnimation: () => useScrollAnimation
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
|
|
32
|
+
// src/hooks/useScrollAnimation.ts
|
|
33
|
+
var import_react = require("react");
|
|
34
|
+
var animationStyles = {
|
|
35
|
+
fadeIn: {
|
|
36
|
+
initial: { opacity: 0 },
|
|
37
|
+
animate: { opacity: 1 }
|
|
38
|
+
},
|
|
39
|
+
fadeInUp: {
|
|
40
|
+
initial: { opacity: 0, transform: "translateY(40px)" },
|
|
41
|
+
animate: { opacity: 1, transform: "translateY(0)" }
|
|
42
|
+
},
|
|
43
|
+
fadeInDown: {
|
|
44
|
+
initial: { opacity: 0, transform: "translateY(-40px)" },
|
|
45
|
+
animate: { opacity: 1, transform: "translateY(0)" }
|
|
46
|
+
},
|
|
47
|
+
slideInLeft: {
|
|
48
|
+
initial: { opacity: 0, transform: "translateX(-60px)" },
|
|
49
|
+
animate: { opacity: 1, transform: "translateX(0)" }
|
|
50
|
+
},
|
|
51
|
+
slideInRight: {
|
|
52
|
+
initial: { opacity: 0, transform: "translateX(60px)" },
|
|
53
|
+
animate: { opacity: 1, transform: "translateX(0)" }
|
|
54
|
+
},
|
|
55
|
+
scaleUp: {
|
|
56
|
+
initial: { opacity: 0, transform: "scale(0.8)" },
|
|
57
|
+
animate: { opacity: 1, transform: "scale(1)" }
|
|
58
|
+
},
|
|
59
|
+
scaleDown: {
|
|
60
|
+
initial: { opacity: 0, transform: "scale(1.2)" },
|
|
61
|
+
animate: { opacity: 1, transform: "scale(1)" }
|
|
62
|
+
},
|
|
63
|
+
rotateIn: {
|
|
64
|
+
initial: { opacity: 0, transform: "rotate(-10deg) scale(0.9)" },
|
|
65
|
+
animate: { opacity: 1, transform: "rotate(0deg) scale(1)" }
|
|
66
|
+
},
|
|
67
|
+
blurIn: {
|
|
68
|
+
initial: { opacity: 0, filter: "blur(10px)" },
|
|
69
|
+
animate: { opacity: 1, filter: "blur(0px)" }
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function useScrollAnimation(animation, options = {}) {
|
|
73
|
+
const {
|
|
74
|
+
delay = 0,
|
|
75
|
+
duration = 600,
|
|
76
|
+
threshold = 0.1,
|
|
77
|
+
once = true,
|
|
78
|
+
easing = "ease-out"
|
|
79
|
+
} = options;
|
|
80
|
+
const ref = (0, import_react.useRef)(null);
|
|
81
|
+
const [isVisible, setIsVisible] = (0, import_react.useState)(false);
|
|
82
|
+
const [progress, setProgress] = (0, import_react.useState)(0);
|
|
83
|
+
const hasAnimated = (0, import_react.useRef)(false);
|
|
84
|
+
(0, import_react.useEffect)(() => {
|
|
85
|
+
const element = ref.current;
|
|
86
|
+
if (!element) {
|
|
87
|
+
console.log("No element found for animation:", animation);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log("Setting up animation:", animation, "for element:", element);
|
|
91
|
+
const styles = animationStyles[animation];
|
|
92
|
+
Object.assign(element.style, styles.initial);
|
|
93
|
+
console.log("Applied initial styles:", styles.initial);
|
|
94
|
+
const observer = new IntersectionObserver(
|
|
95
|
+
(entries) => {
|
|
96
|
+
entries.forEach((entry) => {
|
|
97
|
+
console.log("Intersection observed:", {
|
|
98
|
+
animation,
|
|
99
|
+
isIntersecting: entry.isIntersecting,
|
|
100
|
+
hasAnimated: hasAnimated.current
|
|
101
|
+
});
|
|
102
|
+
setIsVisible(entry.isIntersecting);
|
|
103
|
+
if (entry.isIntersecting && (!once || !hasAnimated.current)) {
|
|
104
|
+
hasAnimated.current = true;
|
|
105
|
+
console.log("Triggering animation:", animation, "with delay:", delay);
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
if (element) {
|
|
108
|
+
console.log("Applying animation styles:", styles.animate);
|
|
109
|
+
element.style.transition = `all ${duration}ms ${easing}`;
|
|
110
|
+
Object.assign(element.style, styles.animate);
|
|
111
|
+
}
|
|
112
|
+
}, delay);
|
|
113
|
+
}
|
|
114
|
+
if (entry.isIntersecting) {
|
|
115
|
+
const rect = entry.boundingClientRect;
|
|
116
|
+
const windowHeight = window.innerHeight;
|
|
117
|
+
const elementProgress = Math.max(0, Math.min(1, 1 - rect.top / windowHeight));
|
|
118
|
+
setProgress(elementProgress);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
{ threshold }
|
|
123
|
+
);
|
|
124
|
+
observer.observe(element);
|
|
125
|
+
console.log("Observer attached for:", animation);
|
|
126
|
+
const handleScroll = () => {
|
|
127
|
+
if (!element) return;
|
|
128
|
+
const rect = element.getBoundingClientRect();
|
|
129
|
+
const windowHeight = window.innerHeight;
|
|
130
|
+
const elementProgress = Math.max(0, Math.min(1, 1 - rect.top / windowHeight));
|
|
131
|
+
setProgress(elementProgress);
|
|
132
|
+
};
|
|
133
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
134
|
+
return () => {
|
|
135
|
+
console.log("Cleaning up animation:", animation);
|
|
136
|
+
observer.disconnect();
|
|
137
|
+
window.removeEventListener("scroll", handleScroll);
|
|
138
|
+
};
|
|
139
|
+
}, [animation, delay, duration, easing, threshold, once]);
|
|
140
|
+
return { ref, isVisible, progress };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/components/RevealOnScroll.tsx
|
|
144
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
145
|
+
function RevealOnScroll({
|
|
146
|
+
animation,
|
|
147
|
+
children,
|
|
148
|
+
className = "",
|
|
149
|
+
delay,
|
|
150
|
+
duration,
|
|
151
|
+
threshold,
|
|
152
|
+
once,
|
|
153
|
+
easing
|
|
154
|
+
}) {
|
|
155
|
+
const { ref } = useScrollAnimation(animation, {
|
|
156
|
+
delay,
|
|
157
|
+
duration,
|
|
158
|
+
threshold,
|
|
159
|
+
once,
|
|
160
|
+
easing
|
|
161
|
+
});
|
|
162
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, className, children });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/components/ScrollProgress.tsx
|
|
166
|
+
var import_react2 = require("react");
|
|
167
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
168
|
+
function ScrollProgress({
|
|
169
|
+
color = "#6366f1",
|
|
170
|
+
height = 3,
|
|
171
|
+
zIndex = 9999
|
|
172
|
+
}) {
|
|
173
|
+
const [progress, setProgress] = (0, import_react2.useState)(0);
|
|
174
|
+
(0, import_react2.useEffect)(() => {
|
|
175
|
+
const handleScroll = () => {
|
|
176
|
+
const windowHeight = window.innerHeight;
|
|
177
|
+
const documentHeight = document.documentElement.scrollHeight - windowHeight;
|
|
178
|
+
const scrolled = window.scrollY;
|
|
179
|
+
const progress2 = scrolled / documentHeight * 100;
|
|
180
|
+
setProgress(Math.min(100, Math.max(0, progress2)));
|
|
181
|
+
};
|
|
182
|
+
handleScroll();
|
|
183
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
184
|
+
return () => {
|
|
185
|
+
window.removeEventListener("scroll", handleScroll);
|
|
186
|
+
};
|
|
187
|
+
}, []);
|
|
188
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
189
|
+
"div",
|
|
190
|
+
{
|
|
191
|
+
style: {
|
|
192
|
+
position: "fixed",
|
|
193
|
+
top: 0,
|
|
194
|
+
left: 0,
|
|
195
|
+
width: "100%",
|
|
196
|
+
height: `${height}px`,
|
|
197
|
+
zIndex,
|
|
198
|
+
pointerEvents: "none",
|
|
199
|
+
background: "transparent"
|
|
200
|
+
},
|
|
201
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
202
|
+
"div",
|
|
203
|
+
{
|
|
204
|
+
style: {
|
|
205
|
+
height: "100%",
|
|
206
|
+
width: `${progress}%`,
|
|
207
|
+
background: color,
|
|
208
|
+
transition: "width 0.1s ease-out"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/components/ParallaxSection.tsx
|
|
217
|
+
var import_react3 = require("react");
|
|
218
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
219
|
+
function ParallaxSection({
|
|
220
|
+
speed = 0.5,
|
|
221
|
+
children,
|
|
222
|
+
className = ""
|
|
223
|
+
}) {
|
|
224
|
+
const ref = (0, import_react3.useRef)(null);
|
|
225
|
+
const [offset, setOffset] = (0, import_react3.useState)(0);
|
|
226
|
+
(0, import_react3.useEffect)(() => {
|
|
227
|
+
const handleScroll = () => {
|
|
228
|
+
if (!ref.current) return;
|
|
229
|
+
const rect = ref.current.getBoundingClientRect();
|
|
230
|
+
const scrolled = window.scrollY;
|
|
231
|
+
const elementTop = rect.top + scrolled;
|
|
232
|
+
const windowHeight = window.innerHeight;
|
|
233
|
+
if (rect.top < windowHeight && rect.bottom > 0) {
|
|
234
|
+
const parallaxOffset = (scrolled - elementTop) * (speed - 1);
|
|
235
|
+
setOffset(parallaxOffset);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
handleScroll();
|
|
239
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
240
|
+
return () => {
|
|
241
|
+
window.removeEventListener("scroll", handleScroll);
|
|
242
|
+
};
|
|
243
|
+
}, [speed]);
|
|
244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref, className: `relative overflow-hidden ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
245
|
+
"div",
|
|
246
|
+
{
|
|
247
|
+
style: {
|
|
248
|
+
transform: `translateY(${offset}px)`,
|
|
249
|
+
willChange: "transform"
|
|
250
|
+
},
|
|
251
|
+
children
|
|
252
|
+
}
|
|
253
|
+
) });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/components/CountOnScroll.tsx
|
|
257
|
+
var import_react4 = require("react");
|
|
258
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
259
|
+
function CountOnScroll({
|
|
260
|
+
from,
|
|
261
|
+
to,
|
|
262
|
+
duration = 2e3,
|
|
263
|
+
formatFn = (value) => value.toLocaleString(),
|
|
264
|
+
className = ""
|
|
265
|
+
}) {
|
|
266
|
+
const ref = (0, import_react4.useRef)(null);
|
|
267
|
+
const [count, setCount] = (0, import_react4.useState)(from);
|
|
268
|
+
const [hasAnimated, setHasAnimated] = (0, import_react4.useState)(false);
|
|
269
|
+
(0, import_react4.useEffect)(() => {
|
|
270
|
+
const element = ref.current;
|
|
271
|
+
if (!element) return;
|
|
272
|
+
const observer = new IntersectionObserver(
|
|
273
|
+
(entries) => {
|
|
274
|
+
entries.forEach((entry) => {
|
|
275
|
+
if (entry.isIntersecting && !hasAnimated) {
|
|
276
|
+
setHasAnimated(true);
|
|
277
|
+
const startTime = Date.now();
|
|
278
|
+
const difference = to - from;
|
|
279
|
+
const animate = () => {
|
|
280
|
+
const elapsed = Date.now() - startTime;
|
|
281
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
282
|
+
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
|
283
|
+
const currentCount = from + difference * easeOutQuart;
|
|
284
|
+
setCount(currentCount);
|
|
285
|
+
if (progress < 1) {
|
|
286
|
+
requestAnimationFrame(animate);
|
|
287
|
+
} else {
|
|
288
|
+
setCount(to);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
requestAnimationFrame(animate);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
{ threshold: 0.3 }
|
|
296
|
+
);
|
|
297
|
+
observer.observe(element);
|
|
298
|
+
return () => {
|
|
299
|
+
observer.disconnect();
|
|
300
|
+
};
|
|
301
|
+
}, [from, to, duration, hasAnimated]);
|
|
302
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, className, children: formatFn(Math.round(count)) });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/components/StaggerChildren.tsx
|
|
306
|
+
var import_react5 = require("react");
|
|
307
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
308
|
+
function StaggerChildren({
|
|
309
|
+
animation,
|
|
310
|
+
staggerDelay = 100,
|
|
311
|
+
children,
|
|
312
|
+
className = "",
|
|
313
|
+
duration,
|
|
314
|
+
threshold,
|
|
315
|
+
once,
|
|
316
|
+
easing
|
|
317
|
+
}) {
|
|
318
|
+
const childArray = import_react5.Children.toArray(children);
|
|
319
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, children: childArray.map((child, index) => {
|
|
320
|
+
if (!(0, import_react5.isValidElement)(child)) return child;
|
|
321
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
322
|
+
RevealOnScroll,
|
|
323
|
+
{
|
|
324
|
+
animation,
|
|
325
|
+
delay: index * staggerDelay,
|
|
326
|
+
duration,
|
|
327
|
+
threshold,
|
|
328
|
+
once,
|
|
329
|
+
easing,
|
|
330
|
+
children: child
|
|
331
|
+
},
|
|
332
|
+
index
|
|
333
|
+
);
|
|
334
|
+
}) });
|
|
335
|
+
}
|
|
336
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
337
|
+
0 && (module.exports = {
|
|
338
|
+
CountOnScroll,
|
|
339
|
+
ParallaxSection,
|
|
340
|
+
RevealOnScroll,
|
|
341
|
+
ScrollProgress,
|
|
342
|
+
StaggerChildren,
|
|
343
|
+
useScrollAnimation
|
|
344
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// src/hooks/useScrollAnimation.ts
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
var animationStyles = {
|
|
4
|
+
fadeIn: {
|
|
5
|
+
initial: { opacity: 0 },
|
|
6
|
+
animate: { opacity: 1 }
|
|
7
|
+
},
|
|
8
|
+
fadeInUp: {
|
|
9
|
+
initial: { opacity: 0, transform: "translateY(40px)" },
|
|
10
|
+
animate: { opacity: 1, transform: "translateY(0)" }
|
|
11
|
+
},
|
|
12
|
+
fadeInDown: {
|
|
13
|
+
initial: { opacity: 0, transform: "translateY(-40px)" },
|
|
14
|
+
animate: { opacity: 1, transform: "translateY(0)" }
|
|
15
|
+
},
|
|
16
|
+
slideInLeft: {
|
|
17
|
+
initial: { opacity: 0, transform: "translateX(-60px)" },
|
|
18
|
+
animate: { opacity: 1, transform: "translateX(0)" }
|
|
19
|
+
},
|
|
20
|
+
slideInRight: {
|
|
21
|
+
initial: { opacity: 0, transform: "translateX(60px)" },
|
|
22
|
+
animate: { opacity: 1, transform: "translateX(0)" }
|
|
23
|
+
},
|
|
24
|
+
scaleUp: {
|
|
25
|
+
initial: { opacity: 0, transform: "scale(0.8)" },
|
|
26
|
+
animate: { opacity: 1, transform: "scale(1)" }
|
|
27
|
+
},
|
|
28
|
+
scaleDown: {
|
|
29
|
+
initial: { opacity: 0, transform: "scale(1.2)" },
|
|
30
|
+
animate: { opacity: 1, transform: "scale(1)" }
|
|
31
|
+
},
|
|
32
|
+
rotateIn: {
|
|
33
|
+
initial: { opacity: 0, transform: "rotate(-10deg) scale(0.9)" },
|
|
34
|
+
animate: { opacity: 1, transform: "rotate(0deg) scale(1)" }
|
|
35
|
+
},
|
|
36
|
+
blurIn: {
|
|
37
|
+
initial: { opacity: 0, filter: "blur(10px)" },
|
|
38
|
+
animate: { opacity: 1, filter: "blur(0px)" }
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function useScrollAnimation(animation, options = {}) {
|
|
42
|
+
const {
|
|
43
|
+
delay = 0,
|
|
44
|
+
duration = 600,
|
|
45
|
+
threshold = 0.1,
|
|
46
|
+
once = true,
|
|
47
|
+
easing = "ease-out"
|
|
48
|
+
} = options;
|
|
49
|
+
const ref = useRef(null);
|
|
50
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
51
|
+
const [progress, setProgress] = useState(0);
|
|
52
|
+
const hasAnimated = useRef(false);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const element = ref.current;
|
|
55
|
+
if (!element) {
|
|
56
|
+
console.log("No element found for animation:", animation);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log("Setting up animation:", animation, "for element:", element);
|
|
60
|
+
const styles = animationStyles[animation];
|
|
61
|
+
Object.assign(element.style, styles.initial);
|
|
62
|
+
console.log("Applied initial styles:", styles.initial);
|
|
63
|
+
const observer = new IntersectionObserver(
|
|
64
|
+
(entries) => {
|
|
65
|
+
entries.forEach((entry) => {
|
|
66
|
+
console.log("Intersection observed:", {
|
|
67
|
+
animation,
|
|
68
|
+
isIntersecting: entry.isIntersecting,
|
|
69
|
+
hasAnimated: hasAnimated.current
|
|
70
|
+
});
|
|
71
|
+
setIsVisible(entry.isIntersecting);
|
|
72
|
+
if (entry.isIntersecting && (!once || !hasAnimated.current)) {
|
|
73
|
+
hasAnimated.current = true;
|
|
74
|
+
console.log("Triggering animation:", animation, "with delay:", delay);
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
if (element) {
|
|
77
|
+
console.log("Applying animation styles:", styles.animate);
|
|
78
|
+
element.style.transition = `all ${duration}ms ${easing}`;
|
|
79
|
+
Object.assign(element.style, styles.animate);
|
|
80
|
+
}
|
|
81
|
+
}, delay);
|
|
82
|
+
}
|
|
83
|
+
if (entry.isIntersecting) {
|
|
84
|
+
const rect = entry.boundingClientRect;
|
|
85
|
+
const windowHeight = window.innerHeight;
|
|
86
|
+
const elementProgress = Math.max(0, Math.min(1, 1 - rect.top / windowHeight));
|
|
87
|
+
setProgress(elementProgress);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
{ threshold }
|
|
92
|
+
);
|
|
93
|
+
observer.observe(element);
|
|
94
|
+
console.log("Observer attached for:", animation);
|
|
95
|
+
const handleScroll = () => {
|
|
96
|
+
if (!element) return;
|
|
97
|
+
const rect = element.getBoundingClientRect();
|
|
98
|
+
const windowHeight = window.innerHeight;
|
|
99
|
+
const elementProgress = Math.max(0, Math.min(1, 1 - rect.top / windowHeight));
|
|
100
|
+
setProgress(elementProgress);
|
|
101
|
+
};
|
|
102
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
103
|
+
return () => {
|
|
104
|
+
console.log("Cleaning up animation:", animation);
|
|
105
|
+
observer.disconnect();
|
|
106
|
+
window.removeEventListener("scroll", handleScroll);
|
|
107
|
+
};
|
|
108
|
+
}, [animation, delay, duration, easing, threshold, once]);
|
|
109
|
+
return { ref, isVisible, progress };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/components/RevealOnScroll.tsx
|
|
113
|
+
import { jsx } from "react/jsx-runtime";
|
|
114
|
+
function RevealOnScroll({
|
|
115
|
+
animation,
|
|
116
|
+
children,
|
|
117
|
+
className = "",
|
|
118
|
+
delay,
|
|
119
|
+
duration,
|
|
120
|
+
threshold,
|
|
121
|
+
once,
|
|
122
|
+
easing
|
|
123
|
+
}) {
|
|
124
|
+
const { ref } = useScrollAnimation(animation, {
|
|
125
|
+
delay,
|
|
126
|
+
duration,
|
|
127
|
+
threshold,
|
|
128
|
+
once,
|
|
129
|
+
easing
|
|
130
|
+
});
|
|
131
|
+
return /* @__PURE__ */ jsx("div", { ref, className, children });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/components/ScrollProgress.tsx
|
|
135
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
136
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
137
|
+
function ScrollProgress({
|
|
138
|
+
color = "#6366f1",
|
|
139
|
+
height = 3,
|
|
140
|
+
zIndex = 9999
|
|
141
|
+
}) {
|
|
142
|
+
const [progress, setProgress] = useState2(0);
|
|
143
|
+
useEffect2(() => {
|
|
144
|
+
const handleScroll = () => {
|
|
145
|
+
const windowHeight = window.innerHeight;
|
|
146
|
+
const documentHeight = document.documentElement.scrollHeight - windowHeight;
|
|
147
|
+
const scrolled = window.scrollY;
|
|
148
|
+
const progress2 = scrolled / documentHeight * 100;
|
|
149
|
+
setProgress(Math.min(100, Math.max(0, progress2)));
|
|
150
|
+
};
|
|
151
|
+
handleScroll();
|
|
152
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
153
|
+
return () => {
|
|
154
|
+
window.removeEventListener("scroll", handleScroll);
|
|
155
|
+
};
|
|
156
|
+
}, []);
|
|
157
|
+
return /* @__PURE__ */ jsx2(
|
|
158
|
+
"div",
|
|
159
|
+
{
|
|
160
|
+
style: {
|
|
161
|
+
position: "fixed",
|
|
162
|
+
top: 0,
|
|
163
|
+
left: 0,
|
|
164
|
+
width: "100%",
|
|
165
|
+
height: `${height}px`,
|
|
166
|
+
zIndex,
|
|
167
|
+
pointerEvents: "none",
|
|
168
|
+
background: "transparent"
|
|
169
|
+
},
|
|
170
|
+
children: /* @__PURE__ */ jsx2(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
173
|
+
style: {
|
|
174
|
+
height: "100%",
|
|
175
|
+
width: `${progress}%`,
|
|
176
|
+
background: color,
|
|
177
|
+
transition: "width 0.1s ease-out"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/components/ParallaxSection.tsx
|
|
186
|
+
import { useEffect as useEffect3, useRef as useRef2, useState as useState3 } from "react";
|
|
187
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
188
|
+
function ParallaxSection({
|
|
189
|
+
speed = 0.5,
|
|
190
|
+
children,
|
|
191
|
+
className = ""
|
|
192
|
+
}) {
|
|
193
|
+
const ref = useRef2(null);
|
|
194
|
+
const [offset, setOffset] = useState3(0);
|
|
195
|
+
useEffect3(() => {
|
|
196
|
+
const handleScroll = () => {
|
|
197
|
+
if (!ref.current) return;
|
|
198
|
+
const rect = ref.current.getBoundingClientRect();
|
|
199
|
+
const scrolled = window.scrollY;
|
|
200
|
+
const elementTop = rect.top + scrolled;
|
|
201
|
+
const windowHeight = window.innerHeight;
|
|
202
|
+
if (rect.top < windowHeight && rect.bottom > 0) {
|
|
203
|
+
const parallaxOffset = (scrolled - elementTop) * (speed - 1);
|
|
204
|
+
setOffset(parallaxOffset);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
handleScroll();
|
|
208
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
209
|
+
return () => {
|
|
210
|
+
window.removeEventListener("scroll", handleScroll);
|
|
211
|
+
};
|
|
212
|
+
}, [speed]);
|
|
213
|
+
return /* @__PURE__ */ jsx3("div", { ref, className: `relative overflow-hidden ${className}`, children: /* @__PURE__ */ jsx3(
|
|
214
|
+
"div",
|
|
215
|
+
{
|
|
216
|
+
style: {
|
|
217
|
+
transform: `translateY(${offset}px)`,
|
|
218
|
+
willChange: "transform"
|
|
219
|
+
},
|
|
220
|
+
children
|
|
221
|
+
}
|
|
222
|
+
) });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/components/CountOnScroll.tsx
|
|
226
|
+
import { useEffect as useEffect4, useRef as useRef3, useState as useState4 } from "react";
|
|
227
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
228
|
+
function CountOnScroll({
|
|
229
|
+
from,
|
|
230
|
+
to,
|
|
231
|
+
duration = 2e3,
|
|
232
|
+
formatFn = (value) => value.toLocaleString(),
|
|
233
|
+
className = ""
|
|
234
|
+
}) {
|
|
235
|
+
const ref = useRef3(null);
|
|
236
|
+
const [count, setCount] = useState4(from);
|
|
237
|
+
const [hasAnimated, setHasAnimated] = useState4(false);
|
|
238
|
+
useEffect4(() => {
|
|
239
|
+
const element = ref.current;
|
|
240
|
+
if (!element) return;
|
|
241
|
+
const observer = new IntersectionObserver(
|
|
242
|
+
(entries) => {
|
|
243
|
+
entries.forEach((entry) => {
|
|
244
|
+
if (entry.isIntersecting && !hasAnimated) {
|
|
245
|
+
setHasAnimated(true);
|
|
246
|
+
const startTime = Date.now();
|
|
247
|
+
const difference = to - from;
|
|
248
|
+
const animate = () => {
|
|
249
|
+
const elapsed = Date.now() - startTime;
|
|
250
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
251
|
+
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
|
|
252
|
+
const currentCount = from + difference * easeOutQuart;
|
|
253
|
+
setCount(currentCount);
|
|
254
|
+
if (progress < 1) {
|
|
255
|
+
requestAnimationFrame(animate);
|
|
256
|
+
} else {
|
|
257
|
+
setCount(to);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
requestAnimationFrame(animate);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
{ threshold: 0.3 }
|
|
265
|
+
);
|
|
266
|
+
observer.observe(element);
|
|
267
|
+
return () => {
|
|
268
|
+
observer.disconnect();
|
|
269
|
+
};
|
|
270
|
+
}, [from, to, duration, hasAnimated]);
|
|
271
|
+
return /* @__PURE__ */ jsx4("div", { ref, className, children: formatFn(Math.round(count)) });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/components/StaggerChildren.tsx
|
|
275
|
+
import { Children, isValidElement } from "react";
|
|
276
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
277
|
+
function StaggerChildren({
|
|
278
|
+
animation,
|
|
279
|
+
staggerDelay = 100,
|
|
280
|
+
children,
|
|
281
|
+
className = "",
|
|
282
|
+
duration,
|
|
283
|
+
threshold,
|
|
284
|
+
once,
|
|
285
|
+
easing
|
|
286
|
+
}) {
|
|
287
|
+
const childArray = Children.toArray(children);
|
|
288
|
+
return /* @__PURE__ */ jsx5("div", { className, children: childArray.map((child, index) => {
|
|
289
|
+
if (!isValidElement(child)) return child;
|
|
290
|
+
return /* @__PURE__ */ jsx5(
|
|
291
|
+
RevealOnScroll,
|
|
292
|
+
{
|
|
293
|
+
animation,
|
|
294
|
+
delay: index * staggerDelay,
|
|
295
|
+
duration,
|
|
296
|
+
threshold,
|
|
297
|
+
once,
|
|
298
|
+
easing,
|
|
299
|
+
children: child
|
|
300
|
+
},
|
|
301
|
+
index
|
|
302
|
+
);
|
|
303
|
+
}) });
|
|
304
|
+
}
|
|
305
|
+
export {
|
|
306
|
+
CountOnScroll,
|
|
307
|
+
ParallaxSection,
|
|
308
|
+
RevealOnScroll,
|
|
309
|
+
ScrollProgress,
|
|
310
|
+
StaggerChildren,
|
|
311
|
+
useScrollAnimation
|
|
312
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chemmangat/easy-scroll",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight scroll-driven animations using native CSS Scroll Timeline API. Zero dependencies.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
21
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"react",
|
|
26
|
+
"scroll",
|
|
27
|
+
"animation",
|
|
28
|
+
"intersection-observer",
|
|
29
|
+
"scroll-timeline",
|
|
30
|
+
"nextjs",
|
|
31
|
+
"typescript",
|
|
32
|
+
"zero-dependencies"
|
|
33
|
+
],
|
|
34
|
+
"author": "Chemmangat",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/yourusername/react-scroll-motion"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/yourusername/react-scroll-motion/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/yourusername/react-scroll-motion#readme",
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": "^18.0.0",
|
|
46
|
+
"react-dom": "^18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/react": "^18.0.0",
|
|
50
|
+
"@types/react-dom": "^18.0.0",
|
|
51
|
+
"react": "^18.0.0",
|
|
52
|
+
"react-dom": "^18.0.0",
|
|
53
|
+
"tsup": "^8.0.0",
|
|
54
|
+
"typescript": "^5.0.0"
|
|
55
|
+
}
|
|
56
|
+
}
|