@needle-tools/engine 4.9.0-next.43185 → 4.9.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/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/components.needle.json +1 -1
- package/dist/{needle-engine.bundle-CZSmogSi.min.js → needle-engine.bundle-B1gr_nQ0.min.js} +119 -119
- package/dist/{needle-engine.bundle-Bs33m9iI.js → needle-engine.bundle-BikYBC35.js} +5444 -5405
- package/dist/{needle-engine.bundle-Bf4EhG38.umd.cjs → needle-engine.bundle-DrlDKOar.umd.cjs} +128 -128
- package/dist/needle-engine.js +2 -2
- package/dist/needle-engine.min.js +1 -1
- package/dist/needle-engine.umd.cjs +1 -1
- package/lib/engine-components/Component.d.ts +8 -5
- package/lib/engine-components/Component.js +8 -5
- package/lib/engine-components/Component.js.map +1 -1
- package/lib/engine-components/physics/Attractor.d.ts +12 -0
- package/lib/engine-components/physics/Attractor.js +14 -2
- package/lib/engine-components/physics/Attractor.js.map +1 -1
- package/lib/engine-components/web/Clickthrough.d.ts +1 -0
- package/lib/engine-components/web/Clickthrough.js +4 -2
- package/lib/engine-components/web/Clickthrough.js.map +1 -1
- package/lib/engine-components/web/CursorFollow.d.ts +9 -0
- package/lib/engine-components/web/CursorFollow.js +17 -0
- package/lib/engine-components/web/CursorFollow.js.map +1 -1
- package/lib/engine-components/web/ScrollFollow.d.ts +29 -3
- package/lib/engine-components/web/ScrollFollow.js +91 -20
- package/lib/engine-components/web/ScrollFollow.js.map +1 -1
- package/package.json +2 -2
- package/src/engine-components/Component.ts +8 -5
- package/src/engine-components/physics/Attractor.ts +14 -4
- package/src/engine-components/web/Clickthrough.ts +6 -2
- package/src/engine-components/web/CursorFollow.ts +17 -2
- package/src/engine-components/web/ScrollFollow.ts +93 -23
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { Object3D } from "three";
|
|
2
|
-
|
|
1
|
+
import { Box3, Object3D } from "three";
|
|
2
|
+
|
|
3
3
|
import { Mathf } from "../../engine/engine_math.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { EventList } from "../EventList.js";
|
|
4
|
+
import { serializable } from "../../engine/engine_serialization.js";
|
|
5
|
+
import { getBoundingBox } from "../../engine/engine_three_utils.js";
|
|
7
6
|
import { Animation } from "../Animation.js";
|
|
7
|
+
import { Animator } from "../Animator.js";
|
|
8
8
|
import { AudioSource } from "../AudioSource.js";
|
|
9
|
+
import { Behaviour } from "../Component.js";
|
|
10
|
+
import { EventList } from "../EventList.js";
|
|
11
|
+
import { Light } from "../Light.js";
|
|
12
|
+
import { SplineWalker } from "../splines/SplineWalker.js";
|
|
13
|
+
import { PlayableDirector } from "../timeline/PlayableDirector.js";
|
|
9
14
|
|
|
10
15
|
type ScrollFollowEvent = {
|
|
11
16
|
/** Event type */
|
|
@@ -19,10 +24,23 @@ type ScrollFollowEvent = {
|
|
|
19
24
|
defaultPrevented: boolean,
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The ScrollFollow component allows you to link the scroll position of the page (or a specific element) to one or more target objects.
|
|
29
|
+
* This can be used to create scroll-based animations, audio playback, or other effects. For example you can link the scroll position to a timeline (PlayableDirector) to create scroll-based storytelling effects or to an Animator component to change the animation state based on scroll.
|
|
30
|
+
*
|
|
31
|
+
* @link Example at https://scrollytelling-2-z23hmxby7c6x-u30ld.needle.run/
|
|
32
|
+
*/
|
|
22
33
|
export class ScrollFollow extends Behaviour {
|
|
23
34
|
|
|
24
35
|
/**
|
|
25
36
|
* Target object(s) to follow the scroll position of the page. If null, the main camera is used.
|
|
37
|
+
*
|
|
38
|
+
* Supported target types:
|
|
39
|
+
* - PlayableDirector (timeline), the scroll position will be mapped to the timeline time
|
|
40
|
+
* - Animator, the scroll position will be set to a float parameter named "scroll"
|
|
41
|
+
* - Animation, the scroll position will be mapped to the animation time
|
|
42
|
+
* - AudioSource, the scroll position will be mapped to the audio time
|
|
43
|
+
* - Any object with a `scroll` property (number or function)
|
|
26
44
|
*/
|
|
27
45
|
@serializable([Behaviour, Object3D])
|
|
28
46
|
target: object[] | object | null = null;
|
|
@@ -34,8 +52,23 @@ export class ScrollFollow extends Behaviour {
|
|
|
34
52
|
@serializable()
|
|
35
53
|
damping: number = 0;
|
|
36
54
|
|
|
55
|
+
/**
|
|
56
|
+
* If true, the scroll value will be inverted (e.g. scrolling down will result in a value of 0)
|
|
57
|
+
* @default false
|
|
58
|
+
*/
|
|
59
|
+
@serializable()
|
|
60
|
+
invert: boolean = false;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* If set, the scroll position will be read from the specified element instead of the window.
|
|
64
|
+
* Use a CSS selector to specify the element, e.g. `#my-scrollable-div` or `.scroll-container`.
|
|
65
|
+
* @default null
|
|
66
|
+
*/
|
|
67
|
+
@serializable()
|
|
68
|
+
htmlSelector: string | null = null;
|
|
69
|
+
|
|
37
70
|
@serializable()
|
|
38
|
-
mode: "window" = "window";
|
|
71
|
+
private mode: "window" = "window";
|
|
39
72
|
|
|
40
73
|
/**
|
|
41
74
|
* Event fired when the scroll position changes
|
|
@@ -56,23 +89,27 @@ export class ScrollFollow extends Behaviour {
|
|
|
56
89
|
|
|
57
90
|
/** @internal */
|
|
58
91
|
onEnable() {
|
|
59
|
-
window.addEventListener("wheel", this.
|
|
92
|
+
window.addEventListener("wheel", this.updateCurrentScrollValue, { passive: true });
|
|
60
93
|
this.applied_value = -1;
|
|
61
|
-
this.updateCurrentScroll();
|
|
62
94
|
}
|
|
63
95
|
|
|
64
96
|
/** @internal */
|
|
65
97
|
onDisable() {
|
|
66
|
-
window.removeEventListener("wheel", this.
|
|
98
|
+
window.removeEventListener("wheel", this.updateCurrentScrollValue);
|
|
67
99
|
}
|
|
68
100
|
|
|
69
101
|
/** @internal */
|
|
70
102
|
lateUpdate() {
|
|
71
103
|
|
|
104
|
+
this.updateCurrentScrollValue();
|
|
105
|
+
|
|
72
106
|
// apply damping if any
|
|
73
107
|
if (this.damping > 0) {
|
|
74
108
|
this.current_value = Mathf.lerp(this.current_value, this.target_value, this.context.time.deltaTime / this.damping);
|
|
75
109
|
}
|
|
110
|
+
else {
|
|
111
|
+
this.current_value = this.target_value;
|
|
112
|
+
}
|
|
76
113
|
|
|
77
114
|
if (this.current_value !== this.applied_value) {
|
|
78
115
|
this.applied_value = this.current_value;
|
|
@@ -94,55 +131,88 @@ export class ScrollFollow extends Behaviour {
|
|
|
94
131
|
// if not prevented apply scroll
|
|
95
132
|
if (!defaultPrevented) {
|
|
96
133
|
|
|
134
|
+
const value = this.invert ? 1 - this.current_value : this.current_value;
|
|
135
|
+
|
|
97
136
|
// apply scroll to target(s)
|
|
98
137
|
if (Array.isArray(this.target)) {
|
|
99
|
-
this.target.forEach(t => t &&
|
|
138
|
+
this.target.forEach(t => t && ScrollFollow.applyScroll(t, value));
|
|
100
139
|
}
|
|
101
140
|
else if (this.target) {
|
|
102
|
-
|
|
141
|
+
ScrollFollow.applyScroll(this.target, value);
|
|
103
142
|
}
|
|
104
143
|
}
|
|
105
144
|
}
|
|
106
145
|
}
|
|
107
146
|
|
|
147
|
+
private _lastSelectorValue: string | null = null;
|
|
148
|
+
private _lastSelectorElement: Element | null = null;
|
|
108
149
|
|
|
109
|
-
private
|
|
150
|
+
private updateCurrentScrollValue = () => {
|
|
110
151
|
|
|
111
152
|
switch (this.mode) {
|
|
112
153
|
case "window":
|
|
113
|
-
|
|
154
|
+
if (this.htmlSelector?.length) {
|
|
155
|
+
if (this.htmlSelector !== this._lastSelectorValue) {
|
|
156
|
+
this._lastSelectorElement = document.querySelector(this.htmlSelector);
|
|
157
|
+
this._lastSelectorValue = this.htmlSelector;
|
|
158
|
+
}
|
|
159
|
+
if (this._lastSelectorElement) {
|
|
160
|
+
const rect = this._lastSelectorElement.getBoundingClientRect();
|
|
161
|
+
this.target_value = -rect.top / (rect.height - window.innerHeight);
|
|
162
|
+
if (isNaN(this.target_value) || !isFinite(this.target_value)) this.target_value = 0;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
this.target_value = window.scrollY / (document.body.scrollHeight - window.innerHeight);
|
|
168
|
+
}
|
|
114
169
|
if (isNaN(this.target_value) || !isFinite(this.target_value)) this.target_value = 0;
|
|
115
170
|
break;
|
|
116
171
|
}
|
|
117
172
|
|
|
118
|
-
if (this.damping <= 0) {
|
|
119
|
-
this.current_value = this.target_value;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
173
|
}
|
|
123
174
|
|
|
124
175
|
|
|
125
|
-
private applyScroll(target: object) {
|
|
176
|
+
private static applyScroll(target: object, value: number) {
|
|
126
177
|
|
|
127
178
|
if (!target) return;
|
|
128
179
|
|
|
129
180
|
if (target instanceof PlayableDirector) {
|
|
130
|
-
target.time =
|
|
181
|
+
target.time = value * target.duration;
|
|
131
182
|
if (!target.isPlaying) target.evaluate();
|
|
132
183
|
}
|
|
184
|
+
else if (target instanceof Animator) {
|
|
185
|
+
target.setFloat("scroll", value);
|
|
186
|
+
}
|
|
133
187
|
else if (target instanceof Animation) {
|
|
134
|
-
target.time =
|
|
188
|
+
target.time = value * target.duration;
|
|
135
189
|
}
|
|
136
190
|
else if (target instanceof AudioSource) {
|
|
137
191
|
if (!target.duration) return;
|
|
138
|
-
target.time =
|
|
192
|
+
target.time = value * target.duration;
|
|
193
|
+
}
|
|
194
|
+
else if (target instanceof SplineWalker) {
|
|
195
|
+
target.position01 = value;
|
|
196
|
+
}
|
|
197
|
+
else if (target instanceof Light) {
|
|
198
|
+
target.intensity = value;
|
|
199
|
+
}
|
|
200
|
+
else if (target instanceof Object3D) {
|
|
201
|
+
// When objects are assigned they're expected to move vertically based on scroll
|
|
202
|
+
if (target["needle:scrollbounds"] === undefined) {
|
|
203
|
+
target["needle:scrollbounds"] = getBoundingBox(target) || null;
|
|
204
|
+
}
|
|
205
|
+
const bounds = target["needle:scrollbounds"] as Box3;
|
|
206
|
+
if (bounds) {
|
|
207
|
+
target.position.y = -bounds.min.y - value * (bounds.max.y - bounds.min.y);
|
|
208
|
+
}
|
|
139
209
|
}
|
|
140
210
|
else if ("scroll" in target) {
|
|
141
211
|
if (typeof target.scroll === "number") {
|
|
142
|
-
target.scroll =
|
|
212
|
+
target.scroll = value;
|
|
143
213
|
}
|
|
144
214
|
else if (typeof target.scroll === "function") {
|
|
145
|
-
target.scroll(
|
|
215
|
+
target.scroll(value);
|
|
146
216
|
}
|
|
147
217
|
}
|
|
148
218
|
}
|