@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 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
+ [![No Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](package.json)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)](tsconfig.json)
7
+ [![Bundle Size](https://img.shields.io/badge/bundle%20size-%3C3KB-success.svg)](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
@@ -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 };
@@ -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
+ }