@riverflowpkg/riverflow 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.
@@ -0,0 +1,244 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ class SimpleScroll {
5
+ constructor(options = {}) {
6
+ this.options = {
7
+ speed: 0.5,
8
+ lerp: 0.1,
9
+ ...options
10
+ };
11
+
12
+ this.body = document.body;
13
+ this.html = document.documentElement;
14
+ this.windowHeight = window.innerHeight;
15
+ this.windowWidth = window.innerWidth;
16
+ this.scrollY = 0;
17
+ this.targetY = 0;
18
+ this.isScrolling = false;
19
+ this.rafId = null;
20
+ this.content = null;
21
+
22
+ // Get speed from attributes
23
+ const speedAttr = this.body.getAttribute('scroll-speed') ||
24
+ this.body.getAttribute('data-scroll-speed');
25
+ if (speedAttr !== null) {
26
+ this.options.speed = parseFloat(speedAttr);
27
+ }
28
+
29
+ this.init();
30
+ }
31
+
32
+ init() {
33
+ // Create a wrapper for content
34
+ this.content = document.createElement('div');
35
+ this.content.className = 'scroll-content';
36
+
37
+ // Move all body children into the wrapper
38
+ while (this.body.firstChild) {
39
+ this.content.appendChild(this.body.firstChild);
40
+ }
41
+ this.body.appendChild(this.content);
42
+
43
+ // Set initial styles
44
+ this.body.style.margin = '0';
45
+ this.body.style.overflow = 'hidden';
46
+ this.body.style.height = '100vh';
47
+ this.body.style.position = 'relative';
48
+
49
+ this.html.style.overflow = 'hidden';
50
+ this.html.style.height = '100%';
51
+
52
+ this.content.style.willChange = 'transform';
53
+ this.content.style.transform = 'translate3d(0, 0, 0)';
54
+
55
+ // Set initial scroll position
56
+ this.targetY = 0;
57
+ this.scrollY = 0;
58
+
59
+ // Add classes
60
+ this.html.classList.add('has-scroll-smooth');
61
+ this.html.classList.add('has-scroll-init');
62
+
63
+ // Setup event listeners
64
+ this.setupVirtualScroll();
65
+ window.addEventListener('resize', () => this.onResize());
66
+
67
+ // Start the scroll loop
68
+ this.loop();
69
+ }
70
+
71
+ setupVirtualScroll() {
72
+ // Handle wheel events
73
+ this.onWheel = this.onWheel.bind(this);
74
+ window.addEventListener('wheel', this.onWheel, { passive: false });
75
+
76
+ // Handle touch events
77
+ this.onTouchStart = this.onTouchStart.bind(this);
78
+ this.onTouchMove = this.onTouchMove.bind(this);
79
+ window.addEventListener('touchstart', this.onTouchStart, { passive: true });
80
+ window.addEventListener('touchmove', this.onTouchMove, { passive: false });
81
+ }
82
+
83
+ onWheel(e) {
84
+ e.preventDefault();
85
+ const delta = e.deltaY || e.wheelDelta || -e.detail;
86
+ this.targetY += delta * this.options.speed;
87
+ this.limitScroll();
88
+ this.startScrolling();
89
+ }
90
+
91
+ onTouchStart(e) {
92
+ this.touchY = e.touches[0].clientY;
93
+ }
94
+
95
+ onTouchMove(e) {
96
+ e.preventDefault();
97
+ const touchY = e.touches[0].clientY;
98
+ const delta = (this.touchY - touchY) * 2;
99
+ this.targetY += delta * this.options.speed;
100
+ this.touchY = touchY;
101
+ this.limitScroll();
102
+ this.startScrolling();
103
+ }
104
+
105
+ limitScroll() {
106
+ const contentHeight = this.content.offsetHeight;
107
+ const maxScroll = Math.max(0, contentHeight - this.windowHeight);
108
+ this.targetY = Math.max(0, Math.min(this.targetY, maxScroll));
109
+ }
110
+
111
+ startScrolling() {
112
+ if (!this.isScrolling) {
113
+ this.isScrolling = true;
114
+ this.html.classList.add('has-scroll-scrolling');
115
+ }
116
+ }
117
+
118
+ stopScrolling() {
119
+ if (this.isScrolling) {
120
+ this.isScrolling = false;
121
+ this.html.classList.remove('has-scroll-scrolling');
122
+ }
123
+ }
124
+
125
+ onResize() {
126
+ this.windowHeight = window.innerHeight;
127
+ this.windowWidth = window.innerWidth;
128
+ this.limitScroll();
129
+ }
130
+
131
+ update() {
132
+ // Smooth interpolation
133
+ const diff = this.targetY - this.scrollY;
134
+ if (Math.abs(diff) > 0.01) {
135
+ this.scrollY += diff * this.options.lerp;
136
+ this.isScrolling = true;
137
+ } else {
138
+ this.scrollY = this.targetY;
139
+ this.stopScrolling();
140
+ }
141
+
142
+ // Apply transform to content
143
+ this.content.style.transform = `translate3d(0, -${this.scrollY}px, 0)`;
144
+
145
+ // Dispatch scroll event
146
+ this.dispatchScrollEvent();
147
+ }
148
+
149
+ loop() {
150
+ this.update();
151
+ this.rafId = requestAnimationFrame(() => this.loop());
152
+ }
153
+
154
+ dispatchScrollEvent() {
155
+ const event = new CustomEvent('scroll', {
156
+ detail: {
157
+ y: this.scrollY,
158
+ targetY: this.targetY,
159
+ limit: Math.max(0, this.content.offsetHeight - this.windowHeight),
160
+ progress: this.scrollY / Math.max(1, this.content.offsetHeight - this.windowHeight),
161
+ speed: this.options.speed
162
+ }
163
+ });
164
+ document.dispatchEvent(event);
165
+ }
166
+
167
+ scrollTo(target, duration = 1000) {
168
+ const startY = this.scrollY;
169
+ let endY = typeof target === 'number' ? target :
170
+ target.getBoundingClientRect().top + this.scrollY;
171
+
172
+ // Clamp end position
173
+ const maxScroll = Math.max(0, this.content.offsetHeight - this.windowHeight);
174
+ endY = Math.max(0, Math.min(endY, maxScroll));
175
+
176
+ const startTime = Date.now();
177
+
178
+ const easeInOut = (t) => {
179
+ return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
180
+ };
181
+
182
+ const animate = () => {
183
+ const elapsed = Date.now() - startTime;
184
+ const progress = Math.min(elapsed / duration, 1);
185
+ const easedProgress = easeInOut(progress);
186
+
187
+ this.targetY = startY + (endY - startY) * easedProgress;
188
+ this.limitScroll();
189
+ this.startScrolling();
190
+
191
+ if (progress < 1) {
192
+ requestAnimationFrame(animate);
193
+ }
194
+ };
195
+
196
+ animate();
197
+ }
198
+
199
+ destroy() {
200
+ if (this.rafId) {
201
+ cancelAnimationFrame(this.rafId);
202
+ }
203
+
204
+ window.removeEventListener('wheel', this.onWheel);
205
+ window.removeEventListener('touchstart', this.onTouchStart);
206
+ window.removeEventListener('touchmove', this.onTouchMove);
207
+ window.removeEventListener('resize', this.onResize);
208
+
209
+ // Restore content
210
+ while (this.content.firstChild) {
211
+ this.body.appendChild(this.content.firstChild);
212
+ }
213
+ this.content.remove();
214
+
215
+ // Restore styles
216
+ this.body.style.margin = '';
217
+ this.body.style.overflow = '';
218
+ this.body.style.height = '';
219
+ this.body.style.position = '';
220
+ this.html.style.overflow = '';
221
+ this.html.style.height = '';
222
+
223
+ this.html.classList.remove('has-scroll-smooth');
224
+ this.html.classList.remove('has-scroll-scrolling');
225
+ this.html.classList.remove('has-scroll-init');
226
+ }
227
+ }
228
+
229
+ // Auto-initialize
230
+ if (document.body.hasAttribute('scroll') || document.body.hasAttribute('data-scroll')) {
231
+ if (document.readyState === 'loading') {
232
+ document.addEventListener('DOMContentLoaded', () => {
233
+ window.simpleScroll = new SimpleScroll();
234
+ });
235
+ } else {
236
+ window.simpleScroll = new SimpleScroll();
237
+ }
238
+ }
239
+
240
+ // Export for npm
241
+ if (typeof module !== 'undefined' && module.exports) {
242
+ module.exports = SimpleScroll;
243
+ }
244
+ })();