@ohos-ports/basicscroll 3.0.4-beta.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Tobias Reich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,594 @@
1
+ # basicScroll
2
+
3
+ [![Donate via PayPal](https://img.shields.io/badge/paypal-donate-009cde.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CYKBESW577YWE)
4
+
5
+ Standalone parallax scrolling for mobile and desktop with CSS variables.
6
+
7
+ basicScroll allows you to change CSS variables depending on the scroll position. Use the variables directly in your CSS to animate whatever you want. Highly inspired by [skrollr](https://github.com/Prinzhorn/skrollr) and [Reactive Animations with CSS Variables](http://slides.com/davidkhourshid/reactanim#/).
8
+
9
+ ## Contents
10
+
11
+ - [Demos](#demos)
12
+ - [Tutorials](#tutorials)
13
+ - [Features](#features)
14
+ - [Requirements](#requirements)
15
+ - [Setup](#setup)
16
+ - [API](#api)
17
+ - [Instance API](#instance-api)
18
+ - [Data](#data)
19
+ - [Related](#related)
20
+ - [Tips](#tips)
21
+
22
+ ## Demos
23
+
24
+ | Name | Description | Link | Author |
25
+ |:-----------|:------------|:------------|:------------|
26
+ | Default | Includes most features | [Try it on CodePen](http://codepen.io/electerious/pen/QGNxxx) |
27
+ | Callback | Animate properties in JS via callbacks | [Try it on CodePen](https://codepen.io/electerious/pen/goZRBv) |
28
+ | Parallax scene | A composition of multiple, moving layers | [Try it on CodePen](http://codepen.io/electerious/pen/gLLozQ) | [@electerious](https://twitter.com/electerious) |
29
+ | Rolling eyes | Custom element to track scrolling | [Try it on CodePen](https://codepen.io/electerious/pen/MZJZxm) | [@electerious](https://twitter.com/electerious) |
30
+ | Headline explosion | Animated letters | [Try it on CodePen](https://codepen.io/electerious/pen/EQzxxJ) | [@electerious](https://twitter.com/electerious) |
31
+ | Scroll and morph | Morph text using CSS clip-path | [Try it on CodePen](https://codepen.io/ainalem/pen/jZzxrP) | [@mikaelainalem](https://twitter.com/mikaelainalem) |
32
+ | Parallax with JS | Several examples and a debug mode | [Try it on CodePen](https://codepen.io/animaticss/pen/rNBJwmq) | [AnimatiCSS](https://www.youtube.com/channel/UC73Tk5wfEBh67Vm7gM_zaAw) |
33
+
34
+ ## Tutorials
35
+
36
+ | Name | Link |
37
+ |:-----------|:------------|
38
+ | 📃 Parallax scrolling with JS controlled CSS variables | [Read it on Medium](https://medium.com/@electerious/parallax-scrolling-with-js-controlled-css-variables-63cfe96820c7) |
39
+ | 🎬 Apple-like scroll animations | [Watch it on YouTube](https://www.youtube.com/watch?v=hPd1srSWDU4) |
40
+ | 🎬 Parallax effect tutorial (🇪🇸) | [Watch it on YouTube](https://www.youtube.com/watch?v=QeRg4t3I2zc) |
41
+
42
+ ## Features
43
+
44
+ - Framework independent
45
+ - Insane performance
46
+ - Support for mobile and desktop
47
+ - CommonJS and AMD support
48
+ - Simple JS API
49
+
50
+ ## Requirements
51
+
52
+ basicScroll depends on the following browser features and APIs:
53
+
54
+ - [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*)
55
+ - [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
56
+ - [window.requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
57
+
58
+ Some of these APIs are capable of being polyfilled in older browsers. Check the linked resources above to determine if you must polyfill to achieve your desired level of browser support.
59
+
60
+ ## Setup
61
+
62
+ We recommend installing basicScroll using [npm](https://npmjs.com) or [yarn](https://yarnpkg.com).
63
+
64
+ ```sh
65
+ npm install basicscroll
66
+ ```
67
+
68
+ ```sh
69
+ yarn add basicscroll
70
+ ```
71
+
72
+ Include the JS file at the end of your `body` tag…
73
+
74
+ ```html
75
+ <script src="dist/basicScroll.min.js"></script>
76
+ ```
77
+
78
+ …or skip the JS file and use basicScroll as a module:
79
+
80
+ ```js
81
+ const basicScroll = require('basicscroll')
82
+ ```
83
+
84
+ ```js
85
+ import * as basicScroll from 'basicscroll'
86
+ ```
87
+
88
+ ### Node.js / HarmonyOS
89
+
90
+ basicScroll normally requires a browser `window`/`document`. A pure-JS DOM polyfill (jsdom) ships as a dependency and a bootstrap entry wires the DOM globals (including `requestAnimationFrame` and window resize tracking) onto the Node.js global scope, so the full API also works outside a browser:
91
+
92
+ ```js
93
+ const basicScroll = require('basicscroll/node-polyfill')
94
+
95
+ const instance = basicScroll.create({
96
+ from: '0px',
97
+ to: '500px',
98
+ props: {
99
+ '--opacity': { from: '0.01', to: '0.99' }
100
+ }
101
+ })
102
+
103
+ instance.start()
104
+ instance.update()
105
+ ```
106
+
107
+ Use `basicscroll` (the plain UMD bundle) when you only need the DOM-free core API in Node.js.
108
+
109
+ ## Usage
110
+
111
+ This demo shows how to to change the opacity of an element when the user scrolls. The element starts to fade as soon as the top of the element reaches the bottom of the viewport. A opacity of `.99` is reached when the middle of the element is in the middle of the viewport.
112
+
113
+ Tip: Animating from `.01` to `.99` avoids the repaints that normally occur when the element changes from fully transparent to translucent and from translucent to fully visible.
114
+
115
+ ```js
116
+ const instance = basicScroll.create({
117
+ elem: document.querySelector('.element'),
118
+ from: 'top-bottom',
119
+ to: 'middle-middle',
120
+ props: {
121
+ '--opacity': {
122
+ from: .01,
123
+ to: .99
124
+ }
125
+ }
126
+ })
127
+
128
+ instance.start()
129
+ ```
130
+
131
+ ```css
132
+ .element {
133
+ /*
134
+ * Use the same CSS variable as specified in our instance.
135
+ */
136
+ opacity: var(--opacity);
137
+ /*
138
+ * The will-change CSS property provides a way for authors to hint browsers about the kind of changes
139
+ * to be expected on an element, so that the browser can setup appropriate optimizations ahead of time
140
+ * before the element is actually changed.
141
+ */
142
+ will-change: opacity;
143
+ }
144
+ ```
145
+
146
+ ## API
147
+
148
+ ### .create(html, opts)
149
+
150
+ Creates a new basicScroll instance.
151
+
152
+ Be sure to assign your instance to a variable. Using your instance, you can…
153
+
154
+ * …start and stop the animation.
155
+ * …check if the instance is active.
156
+ * …get the current props.
157
+ * …recalculate the props when the window size changes.
158
+
159
+ Examples:
160
+
161
+ ```js
162
+ const instance = basicScroll.create({
163
+ from: '0',
164
+ to: '100px',
165
+ props: {
166
+ '--opacity': {
167
+ from: 0,
168
+ to: 1
169
+ }
170
+ }
171
+ })
172
+ ```
173
+
174
+ ```js
175
+ const instance = basicScroll.create({
176
+ elem: document.querySelector('.element'),
177
+ from: 'top-bottom',
178
+ to: 'bottom-top',
179
+ props: {
180
+ '--translateY': {
181
+ from: '0',
182
+ to: '100%',
183
+ timing: 'elasticOut'
184
+ }
185
+ }
186
+ })
187
+ ```
188
+
189
+ ```js
190
+ const instance = basicScroll.create({
191
+ elem: document.querySelector('.element'),
192
+ from: 'top-middle',
193
+ to: 'bottom-middle',
194
+ inside: (instance, percentage, props) => {
195
+ console.log('viewport is inside from and to')
196
+ },
197
+ outside: (instance, percentage, props) => {
198
+ console.log('viewport is outside from and to')
199
+ }
200
+ })
201
+ ```
202
+
203
+ Parameters:
204
+
205
+ - `data` `{Object}` An object of [data](#data).
206
+
207
+ Returns:
208
+
209
+ - `{Object}` The created instance.
210
+
211
+ ## Instance API
212
+
213
+ Each basicScroll instance has a handful of handy functions. Below are all of them along with a short description.
214
+
215
+ ### .start()
216
+
217
+ Starts to animate the instance. basicScroll will track the scroll position and adjust the [props](#props) of the instance accordingly. An update will be performed only when the scroll position has changed.
218
+
219
+ Example:
220
+
221
+ ```js
222
+ instance.start()
223
+ ```
224
+
225
+ ### .stop()
226
+
227
+ Stops to animate the instance. All [props](#props) of the instance will keep their last value.
228
+
229
+ Example:
230
+
231
+ ```js
232
+ instance.stop()
233
+ ```
234
+
235
+ ### .destroy()
236
+
237
+ Destroys the instance. Should be called when the instance is no longer needed. All [props](#props) of the instance will keep their last value.
238
+
239
+ Example:
240
+
241
+ ```js
242
+ instance.destroy()
243
+ ```
244
+
245
+ ### .update()
246
+
247
+ Triggers an update of an instance, even when the instance is currently stopped.
248
+
249
+ Example:
250
+
251
+ ```js
252
+ const props = instance.update()
253
+ ```
254
+
255
+ Returns:
256
+
257
+ - `{Object}` Applied props.
258
+
259
+ ### .calculate()
260
+
261
+ Converts the [start and stop position](#start-and-stop-position) of the instance to absolute values. basicScroll relies on those values to start and stop the animation at the right position. It runs the calculation once during the instance creation. `.calculate()` should be called when elements have altered their position or when the size of the site/viewport has changed.
262
+
263
+ Example:
264
+
265
+ ```js
266
+ instance.calculate()
267
+ ```
268
+
269
+ ### .isActive()
270
+
271
+ Returns `true` when the instance is started and `false` when the instance is stopped.
272
+
273
+ Example:
274
+
275
+ ```js
276
+ instance.isActive()
277
+ ```
278
+
279
+ Returns:
280
+
281
+ - `{Boolean}`
282
+
283
+ ### .getData()
284
+
285
+ Returns calculated data. More or less a parsed version of the [data](#data) used for the instance creation. The data might change when calling the [calculate](#calculate) function.
286
+
287
+ Example:
288
+
289
+ ```js
290
+ instance.getData()
291
+ ```
292
+
293
+ Returns:
294
+
295
+ - `{Object}` Parsed [data](#data).
296
+
297
+ ## Data
298
+
299
+ The data object can include the following properties:
300
+
301
+ ```js
302
+ {
303
+ /*
304
+ * DOM element/node.
305
+ */
306
+ elem: null,
307
+ /*
308
+ * Start and stop position.
309
+ */
310
+ from: null,
311
+ to: null,
312
+ /*
313
+ * Direct mode.
314
+ */
315
+ direct: false,
316
+ /*
317
+ * Track window size changes.
318
+ */
319
+ track: true,
320
+ /*
321
+ * Callback functions.
322
+ */
323
+ inside: (instance, percentage, props) => {},
324
+ outside: (instance, percentage, props) => {},
325
+ /*
326
+ * Props.
327
+ */
328
+ props: {
329
+ /*
330
+ * Property name / CSS Custom Properties.
331
+ */
332
+ '--name': {
333
+ /*
334
+ * Start and end values.
335
+ */
336
+ from: null,
337
+ to: null,
338
+ /*
339
+ * Animation timing.
340
+ */
341
+ timing: 'ease'
342
+ }
343
+ }
344
+ }
345
+ ```
346
+
347
+ ### DOM element/node
348
+
349
+ Type: `Node` Default: `null` Optional: `true`
350
+
351
+ A DOM element/node.
352
+
353
+ The position and size of the element will be used to convert the [start and stop position](#start-and-stop-position) to absolute values. How else is basicScroll supposed to know when to start and stop an animation with relative values?
354
+
355
+ You can skip the property when using absolute values.
356
+
357
+ Example:
358
+
359
+ ```js
360
+ {
361
+ elem: document.querySelector('.element')
362
+ /* ... */
363
+ }
364
+ ```
365
+
366
+ ### Start and stop position
367
+
368
+ Type: `Integer|String` Default: `null` Optional: `false`
369
+
370
+ basicScroll starts to animate the [props](#props) when the scroll position is above `from` and below `to`. Absolute and relative values are allowed.
371
+
372
+ Relative values require a [DOM element/node](#dom-elementnode). The first part of the value describes the element position, the last part describes the viewport position: `<element>-<viewport>`. `middle-bottom` in `from` specifies that the animation starts when the middle of the element reaches the bottom of the viewport.
373
+
374
+ Known relative values: `top-top`, `top-middle`, `top-bottom`, `middle-top`, `middle-middle`, `middle-bottom`, `bottom-top`, `bottom-middle`, `bottom-bottom`
375
+
376
+ It's possible to track a custom anchor when you want to animate for [a specific viewport height](https://github.com/electerious/basicScroll/issues/26#issuecomment-449130809) or when you need to [start and end with an offset](https://github.com/electerious/basicScroll/issues/17#issuecomment-449134650).
377
+
378
+ Examples:
379
+
380
+ ```js
381
+ {
382
+ /* ... */
383
+ from: '0px',
384
+ to: '100px',
385
+ /* ... */
386
+ }
387
+ ```
388
+
389
+ ```js
390
+ {
391
+ /* ... */
392
+ from: 0,
393
+ to: 360,
394
+ /* ... */
395
+ }
396
+ ```
397
+
398
+ ```js
399
+ {
400
+ /* ... */
401
+ from: 'top-middle',
402
+ to: 'bottom-middle',
403
+ /* ... */
404
+ }
405
+ ```
406
+
407
+ ### Direct mode
408
+
409
+ Type: `Boolean|Node` Default: `false` Optional: `true`
410
+
411
+ basicScroll applies all [props](#props) globally by default. This way you can use variables everywhere in your CSS, even when the instance tracks just one element. Set `direct` to `true` or to a DOM element/node to apply all [props](#props) directly to the [DOM element/node](#dom-elementnode) or to the DOM element/node you have specified. This also allows you to animate CSS properties, not just CSS variables.
412
+
413
+ - `false`: Apply props globally (default)
414
+ - `true`: Apply props to the [DOM element/node](#dom-elementnode)
415
+ - `Node`: Apply props to a DOM element/node of your choice
416
+
417
+ Examples:
418
+
419
+ ```html
420
+ <!-- direct: false -->
421
+ <html style="--name: 0;">
422
+ <div class="trackedElem"></div>
423
+ <div class="anotherElem"></div>
424
+ </html>
425
+ ```
426
+
427
+ ```html
428
+ <!-- direct: true -->
429
+ <html>
430
+ <div class="trackedElem" style="--name: 0;"></div>
431
+ <div class="anotherElem"></div>
432
+ </html>
433
+ ```
434
+
435
+ ```html
436
+ <!-- direct: document.querySelector('.anotherElem') -->
437
+ <html>
438
+ <div class="trackedElem"></div>
439
+ <div class="anotherElem" style="--name: 0;"></div>
440
+ </html>
441
+ ```
442
+
443
+ ### Track window size changes
444
+
445
+ Type: `Boolean` Default: `true` Optional: `true`
446
+
447
+ basicScroll automatically recalculates and updates instances when the size of the window changes. You can disable the tracking for each instance individually when you want to take care of it by yourself.
448
+
449
+ Note: basicScroll only tracks the window size. You still must recalculate and update your instances manually when you modify your site. Each modification that changes the layout of the page should trigger such an update in your code.
450
+
451
+ Example:
452
+
453
+ ```js
454
+ const instance = basicScroll.create({
455
+ elem: document.querySelector('.element'),
456
+ from: 'top-bottom',
457
+ to: 'bottom-top',
458
+ track: false,
459
+ props: {
460
+ '--opacity': {
461
+ from: 0,
462
+ to: 1
463
+ }
464
+ }
465
+ })
466
+
467
+ // Recalculate and update your instance manually when the tracking is disabled.
468
+ // Debounce this function in production to avoid unnecessary calculations.
469
+ window.onresize = function() {
470
+
471
+ instance.calculate()
472
+ instance.update()
473
+
474
+ }
475
+ ```
476
+
477
+ ### Callback functions
478
+
479
+ Type: `Function` Default: `() => {}` Optional: `true`
480
+
481
+ - The `inside` callback executes when the user scrolls and the viewport is within the given [start and stop position](#start-and-stop-position).
482
+ - The `outside` callback executes when the user scrolls and the viewport is outside the given [start and stop position](#start-and-stop-position).
483
+
484
+ Both callbacks receive the current instance, a percentage and the calculated properties:
485
+
486
+ - `< 0%` percent = Scroll position is below `from`
487
+ - `= 0%` percent = Scroll position is `from`
488
+ - `= 100%` percent = Scroll position is `to`
489
+ - `> 100%` percent = Scroll position is above `from`
490
+
491
+ Example:
492
+
493
+ ```js
494
+ {
495
+ /* ... */
496
+ inside: (instance, percentage, props) => {},
497
+ outside: (instance, percentage, props) => {},
498
+ /* ... */
499
+ }
500
+ ```
501
+
502
+ ### Props
503
+
504
+ Type: `Object` Default: `{}` Optional: `true`
505
+
506
+ Values to animate when the scroll position changes.
507
+
508
+ Each prop of the object represents a CSS property or CSS Custom Property (CSS variables). Custom CSS properties always start with two dashes. A prop with the name `--name` is accessible with `var(--name)` in CSS.
509
+
510
+ More about [CSS custom properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables).
511
+
512
+ Example:
513
+
514
+ ```js
515
+ {
516
+ /* ... */
517
+ props: {
518
+ '--one-variable': { /* ... */ },
519
+ '--another-variable': { /* ... */ }
520
+ }
521
+ }
522
+ ```
523
+
524
+ ### Start and end values
525
+
526
+ Type: `Integer|String` Default: `null` Optional: `false`
527
+
528
+ Works with all kinds of units. basicScroll uses the unit of `to` when `from` has no unit.
529
+
530
+ Examples:
531
+
532
+ ```js
533
+ '--name': {
534
+ /* ... */
535
+ from: '0',
536
+ to: '100px',
537
+ /* ... */
538
+ }
539
+ ```
540
+
541
+ ```js
542
+ '--name': {
543
+ /* ... */
544
+ from: '50%',
545
+ to: '100%',
546
+ /* ... */
547
+ }
548
+ ```
549
+
550
+ ```js
551
+ '--name': {
552
+ /* ... */
553
+ from: '0',
554
+ to: '1turn',
555
+ /* ... */
556
+ }
557
+ ```
558
+
559
+ ### Animation timing
560
+
561
+ Type: `String|Function` Default: `linear` Optional: `true`
562
+
563
+ A known timing or a custom function. Easing functions get just one argument, which is a value between 0 and 1 (the percentage of how much of the animation is done). The function should return a value between 0 and 1 as well, but for some timings a value less than 0 or greater than 1 is just fine.
564
+
565
+ Known timings: `backInOut`, `backIn`, `backOut`, `bounceInOut`, `bounceIn`, `bounceOut`, `circInOut`, `circIn`, `circOut`, `cubicInOut`, `cubicIn`, `cubicOut`, `elasticInOut`, `elasticIn`, `elasticOut`, `expoInOut`, `expoIn`, `expoOut`, `linear`, `quadInOut`, `quadIn`, `quadOut`, `quartInOut`, `quartIn`, `quartOut`, `quintInOut`, `quintIn`, `quintOut`, `sineInOut`, `sineIn`, `sineOut`
566
+
567
+ Examples:
568
+
569
+ ```js
570
+ '--name': {
571
+ /* ... */
572
+ timing: 'circInOut'
573
+ }
574
+ ```
575
+
576
+ ```js
577
+ '--name': {
578
+ /* ... */
579
+ timing: (t) => t * t
580
+ }
581
+ ```
582
+
583
+ ## Related
584
+
585
+ - [ngx-basicscroll](https://github.com/theunreal/ngx-basicscroll) - Angular wrapper for basicScroll
586
+ - [react-basic-scroll](https://github.com/liorbd/react-basic-scroll) - React wrapper for basicScroll
587
+
588
+ ## Tips
589
+
590
+ - Only animate `transform` and `opacity` and use `will-change` to [hint browsers about the kind of changes](https://developer.mozilla.org/de/docs/Web/CSS/will-change). This way the browser can setup appropriate optimizations ahead of time before the element is actually changed.
591
+ - Keep the amount of instances low. More instances means more checks, calculations and style changes.
592
+ - Don't animate everything at once and don't animate too many properties. Browsers don't like this.
593
+ - Smooth animations by adding a short transition to the element: `transform: translateY(var(--ty)); transition: transform .1s`.
594
+ - basicScroll applies all [props](#props) globally by default. Try to reuse variables across elements instead of creating more instances.
@@ -0,0 +1 @@
1
+ !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).basicScroll=t()}}((function(){return function t(n,o,e){function r(i,c){if(!o[i]){if(!n[i]){var f="function"==typeof require&&require;if(!c&&f)return f(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var s=o[i]={exports:{}};n[i][0].call(s.exports,(function(t){return r(n[i][1][t]||t)}),s,s.exports,t,n,o,e)}return o[i].exports}for(var u="function"==typeof require&&require,i=0;i<e.length;i++)r(e[i]);return r}({1:[function(t,n,o){n.exports=function(t){var n=2.5949095;return(t*=2)<1?t*t*((n+1)*t-n)*.5:.5*((t-=2)*t*((n+1)*t+n)+2)}},{}],2:[function(t,n,o){n.exports=function(t){var n=1.70158;return t*t*((n+1)*t-n)}},{}],3:[function(t,n,o){n.exports=function(t){var n=1.70158;return--t*t*((n+1)*t+n)+1}},{}],4:[function(t,n,o){var e=t("./bounce-out");n.exports=function(t){return t<.5?.5*(1-e(1-2*t)):.5*e(2*t-1)+.5}},{"./bounce-out":6}],5:[function(t,n,o){var e=t("./bounce-out");n.exports=function(t){return 1-e(1-t)}},{"./bounce-out":6}],6:[function(t,n,o){n.exports=function(t){var n=t*t;return t<4/11?7.5625*n:t<8/11?9.075*n-9.9*t+3.4:t<.9?4356/361*n-35442/1805*t+16061/1805:10.8*t*t-20.52*t+10.72}},{}],7:[function(t,n,o){n.exports=function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)}},{}],8:[function(t,n,o){n.exports=function(t){return 1-Math.sqrt(1-t*t)}},{}],9:[function(t,n,o){n.exports=function(t){return Math.sqrt(1- --t*t)}},{}],10:[function(t,n,o){n.exports=function(t){return t<.5?4*t*t*t:.5*Math.pow(2*t-2,3)+1}},{}],11:[function(t,n,o){n.exports=function(t){return t*t*t}},{}],12:[function(t,n,o){n.exports=function(t){var n=t-1;return n*n*n+1}},{}],13:[function(t,n,o){n.exports=function(t){return t<.5?.5*Math.sin(13*Math.PI/2*2*t)*Math.pow(2,10*(2*t-1)):.5*Math.sin(-13*Math.PI/2*(2*t-1+1))*Math.pow(2,-10*(2*t-1))+1}},{}],14:[function(t,n,o){n.exports=function(t){return Math.sin(13*t*Math.PI/2)*Math.pow(2,10*(t-1))}},{}],15:[function(t,n,o){n.exports=function(t){return Math.sin(-13*(t+1)*Math.PI/2)*Math.pow(2,-10*t)+1}},{}],16:[function(t,n,o){n.exports=function(t){return 0===t||1===t?t:t<.5?.5*Math.pow(2,20*t-10):-.5*Math.pow(2,10-20*t)+1}},{}],17:[function(t,n,o){n.exports=function(t){return 0===t?t:Math.pow(2,10*(t-1))}},{}],18:[function(t,n,o){n.exports=function(t){return 1===t?t:1-Math.pow(2,-10*t)}},{}],19:[function(t,n,o){n.exports={backInOut:t("./back-in-out"),backIn:t("./back-in"),backOut:t("./back-out"),bounceInOut:t("./bounce-in-out"),bounceIn:t("./bounce-in"),bounceOut:t("./bounce-out"),circInOut:t("./circ-in-out"),circIn:t("./circ-in"),circOut:t("./circ-out"),cubicInOut:t("./cubic-in-out"),cubicIn:t("./cubic-in"),cubicOut:t("./cubic-out"),elasticInOut:t("./elastic-in-out"),elasticIn:t("./elastic-in"),elasticOut:t("./elastic-out"),expoInOut:t("./expo-in-out"),expoIn:t("./expo-in"),expoOut:t("./expo-out"),linear:t("./linear"),quadInOut:t("./quad-in-out"),quadIn:t("./quad-in"),quadOut:t("./quad-out"),quartInOut:t("./quart-in-out"),quartIn:t("./quart-in"),quartOut:t("./quart-out"),quintInOut:t("./quint-in-out"),quintIn:t("./quint-in"),quintOut:t("./quint-out"),sineInOut:t("./sine-in-out"),sineIn:t("./sine-in"),sineOut:t("./sine-out")}},{"./back-in":2,"./back-in-out":1,"./back-out":3,"./bounce-in":5,"./bounce-in-out":4,"./bounce-out":6,"./circ-in":8,"./circ-in-out":7,"./circ-out":9,"./cubic-in":11,"./cubic-in-out":10,"./cubic-out":12,"./elastic-in":14,"./elastic-in-out":13,"./elastic-out":15,"./expo-in":17,"./expo-in-out":16,"./expo-out":18,"./linear":20,"./quad-in":22,"./quad-in-out":21,"./quad-out":23,"./quart-in":25,"./quart-in-out":24,"./quart-out":26,"./quint-in":28,"./quint-in-out":27,"./quint-out":29,"./sine-in":31,"./sine-in-out":30,"./sine-out":32}],20:[function(t,n,o){n.exports=function(t){return t}},{}],21:[function(t,n,o){n.exports=function(t){return(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1)}},{}],22:[function(t,n,o){n.exports=function(t){return t*t}},{}],23:[function(t,n,o){n.exports=function(t){return-t*(t-2)}},{}],24:[function(t,n,o){n.exports=function(t){return t<.5?8*Math.pow(t,4):-8*Math.pow(t-1,4)+1}},{}],25:[function(t,n,o){n.exports=function(t){return Math.pow(t,4)}},{}],26:[function(t,n,o){n.exports=function(t){return Math.pow(t-1,3)*(1-t)+1}},{}],27:[function(t,n,o){n.exports=function(t){return(t*=2)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)}},{}],28:[function(t,n,o){n.exports=function(t){return t*t*t*t*t}},{}],29:[function(t,n,o){n.exports=function(t){return--t*t*t*t*t+1}},{}],30:[function(t,n,o){n.exports=function(t){return-.5*(Math.cos(Math.PI*t)-1)}},{}],31:[function(t,n,o){n.exports=function(t){var n=Math.cos(t*Math.PI*.5);return Math.abs(n)<1e-14?1:1-n}},{}],32:[function(t,n,o){n.exports=function(t){return Math.sin(t*Math.PI/2)}},{}],33:[function(t,n,o){n.exports=function(t,n){n||(n=[0,""]),t=String(t);var o=parseFloat(t,10);return n[0]=o,n[1]=t.match(/[\d.\-\+]*\s*(.*)/)[1]||"",n}},{}],34:[function(t,n,o){"use strict";Object.defineProperty(o,"__esModule",{value:!0}),o.create=void 0;var e=u(t("parse-unit")),r=u(t("eases"));function u(t){return t&&t.__esModule?t:{default:t}}function i(t){return(i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var c,f,a,s=[],p="undefined"!=typeof window,l=function(){return(document.scrollingElement||document.documentElement).scrollTop},d=function(){return window.innerHeight||window.outerHeight},m=function(t){return!1===isNaN((0,e.default)(t)[0])},b=function(t){var n=(0,e.default)(t);return{value:n[0],unit:n[1]}},h=function(t){return null!==String(t).match(/^[a-z]+-[a-z]+$/)},w=function(t,n){return!0===t?n.elem:t instanceof HTMLElement==!0?n.direct:n.global},y=function(t,n){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:l(),e=arguments.length>3&&void 0!==arguments[3]?arguments[3]:d(),r=n.getBoundingClientRect(),u=t.match(/^[a-z]+/)[0],i=t.match(/[a-z]+$/)[0],c=0;return"top"===i&&(c-=0),"middle"===i&&(c-=e/2),"bottom"===i&&(c-=e),"top"===u&&(c+=r.top+o),"middle"===u&&(c+=r.top+o+r.height/2),"bottom"===u&&(c+=r.top+o+r.height),"".concat(c,"px")},v=function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:l(),o=t.getData(),e=o.to.value-o.from.value,r=n-o.from.value,u=r/(e/100),i=Math.min(Math.max(u,0),100),c=w(o.direct,{global:document.documentElement,elem:o.elem,direct:o.direct}),f=Object.keys(o.props).reduce((function(t,n){var e=o.props[n],r=e.from.unit||e.to.unit,u=e.from.value-e.to.value,c=e.timing(i/100),f=e.from.value-u*c,a=Math.round(1e4*f)/1e4;return t[n]=a+r,t}),{}),a=u>=0&&u<=100,s=u<0||u>100;return!0===a&&o.inside(t,u,f),!0===s&&o.outside(t,u,f),{elem:c,props:f}},x=function(t,n){Object.keys(n).forEach((function(o){return function(t,n){t.style.setProperty(n.key,n.value)}(t,{key:o,value:n[o]})}))};o.create=function(t){var n=null,o=!1,e={isActive:function(){return o},getData:function(){return n},calculate:function(){n=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(null==(t=Object.assign({},t)).inside&&(t.inside=function(){}),null==t.outside&&(t.outside=function(){}),null==t.direct&&(t.direct=!1),null==t.track&&(t.track=!0),null==t.props&&(t.props={}),null==t.from)throw new Error("Missing property `from`");if(null==t.to)throw new Error("Missing property `to`");if("function"!=typeof t.inside)throw new Error("Property `inside` must be undefined or a function");if("function"!=typeof t.outside)throw new Error("Property `outside` must be undefined or a function");if("boolean"!=typeof t.direct&&t.direct instanceof HTMLElement==0)throw new Error("Property `direct` must be undefined, a boolean or a DOM element/node");if(!0===t.direct&&null==t.elem)throw new Error("Property `elem` is required when `direct` is true");if("boolean"!=typeof t.track)throw new Error("Property `track` must be undefined or a boolean");if("object"!==i(t.props))throw new Error("Property `props` must be undefined or an object");if(null==t.elem){if(!1===m(t.from))throw new Error("Property `from` must be a absolute value when no `elem` has been provided");if(!1===m(t.to))throw new Error("Property `to` must be a absolute value when no `elem` has been provided")}else!0===h(t.from)&&(t.from=y(t.from,t.elem)),!0===h(t.to)&&(t.to=y(t.to,t.elem));return t.from=b(t.from),t.to=b(t.to),t.props=Object.keys(t.props).reduce((function(n,o){var e=Object.assign({},t.props[o]);if(!1===m(e.from))throw new Error("Property `from` of prop must be a absolute value");if(!1===m(e.to))throw new Error("Property `from` of prop must be a absolute value");if(e.from=b(e.from),e.to=b(e.to),null==e.timing&&(e.timing=r.default.linear),"string"!=typeof e.timing&&"function"!=typeof e.timing)throw new Error("Property `timing` of prop must be undefined, a string or a function");if("string"==typeof e.timing&&null==r.default[e.timing])throw new Error("Unknown timing for property `timing` of prop");return"string"==typeof e.timing&&(e.timing=r.default[e.timing]),n[o]=e,n}),{}),t}(t)},update:function(){var t=v(e),n=t.elem,o=t.props;return x(n,o),o},start:function(){o=!0},stop:function(){o=!1},destroy:function(){s[u]=void 0}},u=s.push(e)-1;return e.calculate(),e},!0===p?(!function t(n,o){var e=function(){requestAnimationFrame((function(){return t(n,o)}))},r=function(t){return t.filter((function(t){return null!=t&&t.isActive()}))}(s);if(0===r.length)return e();var u=l();if(o===u)return e();o=u,r.map((function(t){return v(t,u)})).forEach((function(t){var n=t.elem,o=t.props;return x(n,o)})),e()}(),window.addEventListener("resize",(c=function(){(function(t){return t.filter((function(t){return null!=t&&t.getData().track}))})(s).forEach((function(t){t.calculate(),t.update()}))},f=50,a=null,function(){for(var t=arguments.length,n=new Array(t),o=0;o<t;o++)n[o]=arguments[o];clearTimeout(a),a=setTimeout((function(){return c.apply(void 0,n)}),f)}))):console.warn("basicScroll is not executing because you are using it in an environment without a `window` object")},{eases:19,"parse-unit":33}]},{},[34])(34)}));
@@ -0,0 +1,60 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Node.js / HarmonyOS bootstrap for basicScroll.
5
+ *
6
+ * basicScroll reads `window`/`document` at load time and uses
7
+ * `requestAnimationFrame` plus a window `resize` listener. This bootstrap
8
+ * wires a pure-JS DOM (jsdom) onto the global scope so those paths
9
+ * (Instance.update(), the rAF animation loop, resize tracking and relative
10
+ * from/to values with `elem`) also work outside a browser, then re-exports
11
+ * the bundled library.
12
+ *
13
+ * Usage (Node.js / openharmony arm64):
14
+ *
15
+ * const basicScroll = require('basicscroll/node-polyfill')
16
+ *
17
+ * const instance = basicScroll.create({
18
+ * from: '0px',
19
+ * to: '500px',
20
+ * props: { '--my-prop': { from: '0px', to: '100px' } }
21
+ * })
22
+ *
23
+ * instance.start()
24
+ * instance.update()
25
+ */
26
+
27
+ const { JSDOM } = require('jsdom')
28
+
29
+ const dom = new JSDOM('<!doctype html><html><body></body></html>', {
30
+ url: 'https://localhost/',
31
+ pretendToBeVisual: true
32
+ })
33
+
34
+ const { window } = dom
35
+
36
+ // Expose the browser globals basicScroll depends on at load time.
37
+ // `pretendToBeVisual: true` provides requestAnimationFrame/cancelAnimationFrame.
38
+ for (const key of [
39
+ 'window',
40
+ 'document',
41
+ 'HTMLElement',
42
+ 'navigator',
43
+ 'requestAnimationFrame',
44
+ 'cancelAnimationFrame'
45
+ ]) {
46
+ if (window[key] === undefined) continue
47
+
48
+ try {
49
+ global[key] = window[key]
50
+ } catch (err) {
51
+ // Some globals (e.g. navigator in Node >= 21) only have a getter
52
+ Object.defineProperty(global, key, {
53
+ value: window[key],
54
+ configurable: true,
55
+ writable: true
56
+ })
57
+ }
58
+ }
59
+
60
+ module.exports = require('./dist/basicScroll.min.js')
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@ohos-ports/basicscroll",
3
+ "version": "3.0.4-beta.0",
4
+ "authors": [
5
+ "Tobias Reich <tobias@electerious.com>"
6
+ ],
7
+ "description": "Standalone parallax scrolling for mobile and desktop with CSS variables",
8
+ "main": "dist/basicScroll.min.js",
9
+ "keywords": [
10
+ "parallax",
11
+ "scroll",
12
+ "scrolling"
13
+ ],
14
+ "scripts": {
15
+ "build": "node build.js"
16
+ },
17
+ "license": "MIT",
18
+ "homepage": "https://github.com/electerious/basicScroll",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/ohos-ports/ohos-ports.git",
22
+ "directory": "ports/basicscroll/3.0.4"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src",
27
+ "node-polyfill.js",
28
+ "verify-r1.js"
29
+ ],
30
+ "dependencies": {
31
+ "eases": "^1.0.8",
32
+ "jsdom": "^30.0.1",
33
+ "parse-unit": "^1.0.1"
34
+ },
35
+ "devDependencies": {
36
+ "rosid-handler-js": "^13.0.0"
37
+ }
38
+ }
@@ -0,0 +1,475 @@
1
+ import parseUnit from 'parse-unit'
2
+ import eases from 'eases'
3
+
4
+ const instances = []
5
+ const isBrowser = typeof window !== 'undefined'
6
+
7
+ /**
8
+ * Debounces a function that will be triggered many times.
9
+ * @param {Function} fn
10
+ * @param {Number} duration
11
+ * @returns {Function}
12
+ */
13
+ const debounce = function(fn, duration) {
14
+
15
+ let timeout = null
16
+
17
+ return (...args) => {
18
+
19
+ clearTimeout(timeout)
20
+
21
+ timeout = setTimeout(() => fn(...args), duration)
22
+
23
+ }
24
+
25
+ }
26
+
27
+ /**
28
+ * Returns all active instances from an array.
29
+ * @param {Array} instances
30
+ * @returns {Array} instances - Active instances.
31
+ */
32
+ const getActiveInstances = function(instances) {
33
+
34
+ return instances.filter((instance) => instance != null && instance.isActive())
35
+
36
+ }
37
+
38
+ /**
39
+ * Returns all tracked instances from an array.
40
+ * @param {Array} instances
41
+ * @returns {Array} instances - Tracked instances.
42
+ */
43
+ const getTrackedInstances = function(instances) {
44
+
45
+ return instances.filter((instance) => instance != null && instance.getData().track)
46
+
47
+ }
48
+
49
+
50
+ /**
51
+ * Returns the number of scrolled pixels.
52
+ * @returns {Number} scrollTop
53
+ */
54
+ const getScrollTop = function() {
55
+
56
+ // Use scrollTop because it's faster than getBoundingClientRect()
57
+ return (document.scrollingElement || document.documentElement).scrollTop
58
+
59
+ }
60
+
61
+ /**
62
+ * Returns the height of the viewport.
63
+ * @returns {Number} viewportHeight
64
+ */
65
+ const getViewportHeight = function() {
66
+
67
+ return (window.innerHeight || window.outerHeight)
68
+
69
+ }
70
+
71
+ /**
72
+ * Checks if a value is absolute.
73
+ * An absolute value must have a value that's not NaN.
74
+ * @param {String|Integer} value
75
+ * @returns {Boolean} isAbsolute
76
+ */
77
+ const isAbsoluteValue = function(value) {
78
+
79
+ return isNaN(parseUnit(value)[0]) === false
80
+
81
+ }
82
+
83
+ /**
84
+ * Parses an absolute value.
85
+ * @param {String|Integer} value
86
+ * @returns {Object} value - Parsed value.
87
+ */
88
+ const parseAbsoluteValue = function(value) {
89
+
90
+ const parsedValue = parseUnit(value)
91
+
92
+ return {
93
+ value: parsedValue[0],
94
+ unit: parsedValue[1]
95
+ }
96
+
97
+ }
98
+
99
+ /**
100
+ * Checks if a value is relative.
101
+ * A relative value must start and end with [a-z] and needs a '-' in the middle.
102
+ * @param {String|Integer} value
103
+ * @returns {Boolean} isRelative
104
+ */
105
+ const isRelativeValue = function(value) {
106
+
107
+ return String(value).match(/^[a-z]+-[a-z]+$/) !== null
108
+
109
+ }
110
+
111
+ /**
112
+ * Returns the property that should be used according to direct.
113
+ * @param {Boolean|Node} direct
114
+ * @param {Object} properties
115
+ * @returns {*}
116
+ */
117
+ const mapDirectToProperty = function(direct, properties) {
118
+
119
+ if (direct === true) return properties.elem
120
+ if (direct instanceof HTMLElement === true) return properties.direct
121
+
122
+ return properties.global
123
+
124
+ }
125
+
126
+ /**
127
+ * Converts a relative value to an absolute value.
128
+ * @param {String} value
129
+ * @param {Node} elem - Anchor of the relative value.
130
+ * @param {?Integer} scrollTop - Pixels scrolled in document.
131
+ * @param {?Integer} viewportHeight - Height of the viewport.
132
+ * @returns {String} value - Absolute value.
133
+ */
134
+ const relativeToAbsoluteValue = function(value, elem, scrollTop = getScrollTop(), viewportHeight = getViewportHeight()) {
135
+
136
+ const elemSize = elem.getBoundingClientRect()
137
+
138
+ const elemAnchor = value.match(/^[a-z]+/)[0]
139
+ const viewportAnchor = value.match(/[a-z]+$/)[0]
140
+
141
+ let y = 0
142
+
143
+ if (viewportAnchor === 'top') y -= 0
144
+ if (viewportAnchor === 'middle') y -= viewportHeight / 2
145
+ if (viewportAnchor === 'bottom') y -= viewportHeight
146
+
147
+ if (elemAnchor === 'top') y += (elemSize.top + scrollTop)
148
+ if (elemAnchor === 'middle') y += (elemSize.top + scrollTop) + elemSize.height / 2
149
+ if (elemAnchor === 'bottom') y += (elemSize.top + scrollTop) + elemSize.height
150
+
151
+ return `${ y }px`
152
+
153
+ }
154
+
155
+ /**
156
+ * Validates data and sets defaults for undefined properties.
157
+ * @param {?Object} data
158
+ * @returns {Object} data - Validated data.
159
+ */
160
+ const validate = function(data = {}) {
161
+
162
+ // Copy root object to avoid changes by reference
163
+ data = Object.assign({}, data)
164
+
165
+ if (data.inside == null) data.inside = () => {}
166
+ if (data.outside == null) data.outside = () => {}
167
+ if (data.direct == null) data.direct = false
168
+ if (data.track == null) data.track = true
169
+ if (data.props == null) data.props = {}
170
+
171
+ if (data.from == null) throw new Error('Missing property `from`')
172
+ if (data.to == null) throw new Error('Missing property `to`')
173
+ if (typeof data.inside !== 'function') throw new Error('Property `inside` must be undefined or a function')
174
+ if (typeof data.outside !== 'function') throw new Error('Property `outside` must be undefined or a function')
175
+ if (typeof data.direct !== 'boolean' && data.direct instanceof HTMLElement === false) throw new Error('Property `direct` must be undefined, a boolean or a DOM element/node')
176
+ if (data.direct === true && data.elem == null) throw new Error('Property `elem` is required when `direct` is true')
177
+ if (typeof data.track !== 'boolean') throw new Error('Property `track` must be undefined or a boolean')
178
+ if (typeof data.props !== 'object') throw new Error('Property `props` must be undefined or an object')
179
+
180
+ if (data.elem == null) {
181
+
182
+ if (isAbsoluteValue(data.from) === false) throw new Error('Property `from` must be a absolute value when no `elem` has been provided')
183
+ if (isAbsoluteValue(data.to) === false) throw new Error('Property `to` must be a absolute value when no `elem` has been provided')
184
+
185
+ } else {
186
+
187
+ if (isRelativeValue(data.from) === true) data.from = relativeToAbsoluteValue(data.from, data.elem)
188
+ if (isRelativeValue(data.to) === true) data.to = relativeToAbsoluteValue(data.to, data.elem)
189
+
190
+ }
191
+
192
+ data.from = parseAbsoluteValue(data.from)
193
+ data.to = parseAbsoluteValue(data.to)
194
+
195
+ // Create a new props object to avoid changes by reference
196
+ data.props = Object.keys(data.props).reduce((acc, key) => {
197
+
198
+ // Copy prop object to avoid changes by reference
199
+ const prop = Object.assign({}, data.props[key])
200
+
201
+ if (isAbsoluteValue(prop.from) === false) throw new Error('Property `from` of prop must be a absolute value')
202
+ if (isAbsoluteValue(prop.to) === false) throw new Error('Property `from` of prop must be a absolute value')
203
+
204
+ prop.from = parseAbsoluteValue(prop.from)
205
+ prop.to = parseAbsoluteValue(prop.to)
206
+
207
+ if (prop.timing == null) prop.timing = eases['linear']
208
+
209
+ if (typeof prop.timing !== 'string' && typeof prop.timing !== 'function') throw new Error('Property `timing` of prop must be undefined, a string or a function')
210
+
211
+ if (typeof prop.timing === 'string' && eases[prop.timing] == null) throw new Error('Unknown timing for property `timing` of prop')
212
+ if (typeof prop.timing === 'string') prop.timing = eases[prop.timing]
213
+
214
+ acc[key] = prop
215
+
216
+ return acc
217
+
218
+ }, {})
219
+
220
+ return data
221
+
222
+ }
223
+
224
+ /**
225
+ * Calculates the props of an instance.
226
+ * @param {Object} instance
227
+ * @param {?Integer} scrollTop - Pixels scrolled in document.
228
+ * @returns {Object} Calculated props and the element to apply styles to.
229
+ */
230
+ const getProps = function(instance, scrollTop = getScrollTop()) {
231
+
232
+ const data = instance.getData()
233
+
234
+ // 100% in pixel
235
+ const total = data.to.value - data.from.value
236
+
237
+ // Pixel scrolled
238
+ const current = scrollTop - data.from.value
239
+
240
+ // Percent scrolled
241
+ const precisePercentage = current / (total / 100)
242
+ const normalizedPercentage = Math.min(Math.max(precisePercentage, 0), 100)
243
+
244
+ // Get the element that should be used according to direct
245
+ const elem = mapDirectToProperty(data.direct, {
246
+ global: document.documentElement,
247
+ elem: data.elem,
248
+ direct: data.direct
249
+ })
250
+
251
+ // Generate an object with all new props
252
+ const props = Object.keys(data.props).reduce((acc, key) => {
253
+
254
+ const prop = data.props[key]
255
+
256
+ // Use the unit of from OR to. It's valid to animate from '0' to '100px' and
257
+ // '0' should be treated as 'px', too. Unit will be an empty string when no unit given.
258
+ const unit = prop.from.unit || prop.to.unit
259
+
260
+ // The value that should be interpolated
261
+ const diff = prop.from.value - prop.to.value
262
+
263
+ // All easing functions only remap a time value, and all have the same signature.
264
+ // Typically a value between 0 and 1, and it returns a new float that has been eased.
265
+ const time = prop.timing(normalizedPercentage / 100)
266
+
267
+ const value = prop.from.value - diff * time
268
+
269
+ // Round to avoid unprecise values.
270
+ // The precision of floating point computations is only as precise as the precision it uses.
271
+ // http://stackoverflow.com/questions/588004/is-floating-point-math-broken
272
+ const rounded = Math.round(value * 10000) / 10000
273
+
274
+ acc[key] = rounded + unit
275
+
276
+ return acc
277
+
278
+ }, {})
279
+
280
+ // Use precise percentage to check if the viewport is between from and to.
281
+ // Would always return true when using the normalized percentage.
282
+ const isInside = (precisePercentage >= 0 && precisePercentage <= 100)
283
+ const isOutside = (precisePercentage < 0 || precisePercentage > 100)
284
+
285
+ // Execute callbacks
286
+ if (isInside === true) data.inside(instance, precisePercentage, props)
287
+ if (isOutside === true) data.outside(instance, precisePercentage, props)
288
+
289
+ return {
290
+ elem,
291
+ props
292
+ }
293
+
294
+ }
295
+
296
+ /**
297
+ * Adds a property with the specified name and value to a given style object.
298
+ * @param {Node} elem - Styles will be applied to this element.
299
+ * @param {Object} prop - Object with a key and value.
300
+ */
301
+ const setProp = function(elem, prop) {
302
+
303
+ elem.style.setProperty(prop.key, prop.value)
304
+
305
+ }
306
+
307
+ /**
308
+ * Adds properties to a given style object.
309
+ * @param {Node} elem - Styles will be applied to this element.
310
+ * @param {Object} props - Object of props.
311
+ */
312
+ const setProps = function(elem, props) {
313
+
314
+ Object.keys(props).forEach((key) => setProp(elem, {
315
+ key: key,
316
+ value: props[key]
317
+ }))
318
+
319
+ }
320
+
321
+ /**
322
+ * Gets and sets new props when the user has scrolled and when there are active instances.
323
+ * This part get executed with every frame. Make sure it's performant as hell.
324
+ * @param {Object} style - Style object.
325
+ * @param {?Integer} previousScrollTop
326
+ * @returns {?*}
327
+ */
328
+ const loop = function(style, previousScrollTop) {
329
+
330
+ // Continue loop
331
+ const repeat = () => {
332
+
333
+ // It depends on the browser, but it turns out that closures
334
+ // are sometimes faster than .bind or .apply.
335
+ requestAnimationFrame(() => loop(style, previousScrollTop))
336
+
337
+ }
338
+
339
+ // Get all active instances
340
+ const activeInstances = getActiveInstances(instances)
341
+
342
+ // Only continue when active instances available
343
+ if (activeInstances.length === 0) return repeat()
344
+
345
+ const scrollTop = getScrollTop()
346
+
347
+ // Only continue when scrollTop has changed
348
+ if (previousScrollTop === scrollTop) return repeat()
349
+ else previousScrollTop = scrollTop
350
+
351
+ // Get and set new props of each instance
352
+ activeInstances
353
+ .map((instance) => getProps(instance, scrollTop))
354
+ .forEach(({ elem, props }) => setProps(elem, props))
355
+
356
+ repeat()
357
+
358
+ }
359
+
360
+ /**
361
+ * Creates a new instance.
362
+ * @param {Object} data
363
+ * @returns {Object} instance
364
+ */
365
+ export const create = function(data) {
366
+
367
+ // Store the parsed data
368
+ let _data = null
369
+
370
+ // Store if instance is started or stopped
371
+ let active = false
372
+
373
+ // Returns if instance is started or stopped
374
+ const _isActive = () => {
375
+
376
+ return active
377
+
378
+ }
379
+
380
+ // Returns the parsed and calculated data
381
+ const _getData = function() {
382
+
383
+ return _data
384
+
385
+ }
386
+
387
+ // Parses and calculates data
388
+ const _calculate = function() {
389
+
390
+ _data = validate(data)
391
+
392
+ }
393
+
394
+ // Update props
395
+ const _update = () => {
396
+
397
+ // Get new props
398
+ const { elem, props } = getProps(instance)
399
+
400
+ // Set new props
401
+ setProps(elem, props)
402
+
403
+ return props
404
+
405
+ }
406
+
407
+ // Starts to animate
408
+ const _start = () => {
409
+
410
+ active = true
411
+
412
+ }
413
+
414
+ // Stops to animate
415
+ const _stop = () => {
416
+
417
+ active = false
418
+
419
+ }
420
+
421
+ // Destroys the instance
422
+ const _destroy = () => {
423
+
424
+ // Replace instance instead of deleting the item to avoid
425
+ // that the index of other instances changes.
426
+ instances[index] = undefined
427
+
428
+ }
429
+
430
+ // Assign instance to a variable so the instance can be used
431
+ // elsewhere in the current function.
432
+ const instance = {
433
+ isActive: _isActive,
434
+ getData: _getData,
435
+ calculate: _calculate,
436
+ update: _update,
437
+ start: _start,
438
+ stop: _stop,
439
+ destroy: _destroy
440
+ }
441
+
442
+ // Store instance in global array and save the index
443
+ const index = instances.push(instance) - 1
444
+
445
+ // Calculate data for the first time
446
+ instance.calculate()
447
+
448
+ return instance
449
+
450
+ }
451
+
452
+ // Only run basicScroll when executed in a browser environment
453
+ if (isBrowser === true) {
454
+
455
+ // Start to loop
456
+ loop()
457
+
458
+ // Recalculate and update instances when the window size changes
459
+ window.addEventListener('resize', debounce(() => {
460
+
461
+ // Get all tracked instances
462
+ const trackedInstances = getTrackedInstances(instances)
463
+
464
+ trackedInstances.forEach((instance) => {
465
+ instance.calculate()
466
+ instance.update()
467
+ })
468
+
469
+ }, 50))
470
+
471
+ } else {
472
+
473
+ console.warn('basicScroll is not executing because you are using it in an environment without a `window` object')
474
+
475
+ }
package/verify-r1.js ADDED
@@ -0,0 +1,83 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Round 1 verification: basicscroll + jsdom DOM polyfill on Node.js (openharmony arm64).
5
+ * Exercises: create(), start(), update(), relative from/to with elem, rAF loop, resize tracking.
6
+ */
7
+
8
+ const assert = require('assert')
9
+
10
+ // Load via the new polyfill bootstrap (wires jsdom window/document/rAF globals)
11
+ const basicScroll = require('./node-polyfill')
12
+
13
+ // Globals must now be present (loop started at module load, otherwise a
14
+ // "no window object" warning would have been printed)
15
+ assert.strictEqual(typeof global.window, 'object', 'window global missing')
16
+ assert.strictEqual(typeof global.document, 'object', 'document global missing')
17
+ assert.strictEqual(typeof global.requestAnimationFrame, 'function', 'requestAnimationFrame missing')
18
+ assert.strictEqual(typeof global.HTMLElement, 'function', 'HTMLElement missing')
19
+ console.log('[1/6] jsdom globals wired (window/document/rAF/HTMLElement)')
20
+
21
+ // Core API + update() with absolute values, applied to document.documentElement
22
+ const results = []
23
+ const instance = basicScroll.create({
24
+ from: '0px',
25
+ to: '100px',
26
+ props: {
27
+ '--opacity': { from: '0.01', to: '0.99' }
28
+ },
29
+ inside: (i, percentage) => results.push(['inside', percentage])
30
+ })
31
+ instance.start()
32
+ assert.strictEqual(instance.isActive(), true, 'instance should be active after start()')
33
+
34
+ const props = instance.update()
35
+ assert.deepStrictEqual(props, { '--opacity': '0.01px' === '0.01px' ? '0.01' : props['--opacity'] }, 'unexpected props')
36
+ const applied = global.document.documentElement.style.getPropertyValue('--opacity')
37
+ assert.strictEqual(applied, '0.01', 'prop not applied to documentElement.style')
38
+ console.log('[2/6] create()/start()/update() works, prop applied:', applied)
39
+
40
+ // Mid-range calculation
41
+ global.document.documentElement.scrollTop = 50
42
+ const mid = instance.update()
43
+ assert.strictEqual(mid['--opacity'], '0.5', 'interpolated value wrong, got ' + mid['--opacity'])
44
+ assert.deepStrictEqual(results[1], ['inside', 50], 'inside callback not fired at 50%')
45
+ assert.deepStrictEqual(results[0], ['inside', 0], 'inside callback not fired at 0%')
46
+ console.log('[3/6] update() interpolation + inside callback at scrollTop=50 ->', mid['--opacity'])
47
+
48
+ // Relative from/to values with elem (jsdom element)
49
+ const elem = global.document.createElement('div')
50
+ global.document.body.appendChild(elem)
51
+ const relInstance = basicScroll.create({
52
+ elem: elem,
53
+ direct: true,
54
+ from: 'top-top',
55
+ to: 'bottom-bottom',
56
+ props: { '--move': { from: '0px', to: '100px' } }
57
+ })
58
+ const relProps = relInstance.update()
59
+ assert.ok(typeof relProps['--move'] === 'string' && relProps['--move'].endsWith('px'), 'relative instance update failed')
60
+ assert.strictEqual(elem.style.getPropertyValue('--move'), relProps['--move'], 'prop not applied to elem')
61
+ console.log('[4/6] relative from/to with elem + direct:true works ->', relProps['--move'])
62
+
63
+ // rAF loop: ran at module load without warnings; force a scheduled frame callback
64
+ setTimeout(() => {
65
+ // Resize tracking: dispatch resize, debounced handler recalcs+updates tracked instances
66
+ global.window.dispatchEvent(new global.window.Event('resize'))
67
+
68
+ setTimeout(() => {
69
+ global.document.documentElement.scrollTop = 100
70
+ const end = instance.update()
71
+ assert.strictEqual(end['--opacity'], '0.99', 'end value wrong')
72
+ console.log('[5/6] resize dispatch handled, update() at scrollTop=100 ->', end['--opacity'])
73
+
74
+ // stop/destroy
75
+ instance.stop()
76
+ assert.strictEqual(instance.isActive(), false, 'instance should be inactive after stop()')
77
+ relInstance.destroy()
78
+ console.log('[6/6] stop()/destroy() OK')
79
+
80
+ console.log('ALL CHECKS PASSED: basicscroll animation path functional in Node.js via jsdom polyfill')
81
+ process.exit(0)
82
+ }, 150)
83
+ }, 50)