@vectoriox/iox-ui 5.74.7 → 5.75.2
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/fesm2022/vectoriox-iox-ui-media-core.mjs +88 -38
- package/fesm2022/vectoriox-iox-ui-media-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs +6 -5
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs.map +1 -1
- package/media-core/index.d.ts +42 -10
- package/package.json +1 -1
- package/src/media-core/frames.ts +98 -33
- package/src/scene-core/scroll-driver.ts +6 -5
|
@@ -86,9 +86,22 @@ function framePosition(played, count) {
|
|
|
86
86
|
}
|
|
87
87
|
/** On a Video whose pictures are drawing: the poster has done its job and steps aside. */
|
|
88
88
|
const FRAMES_SHOWING_ATTRIBUTE = 'data-iox-frames-showing';
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Below this many pictures a second the film stops following the scroll exactly and lands on its own. A landing scroll
|
|
91
|
+
* slows exponentially, so the last picture or two would take most of a second as one slow dissolve between two places:
|
|
92
|
+
* the subject showing twice, and then settling. Landing at least this fast keeps the end of the move a move.
|
|
93
|
+
*/
|
|
94
|
+
const FILM_LANDING_SPEED = 10;
|
|
95
|
+
/** With no word from the driver on where the scroll is going, how fast a landing scroll is assumed to slow (per second). */
|
|
96
|
+
const FILM_LANDING_DECAY = 5;
|
|
97
|
+
/** How quickly the film catches up with a scroll that turned back while it was landing ahead of it (per second). */
|
|
98
|
+
const FILM_CATCH_RATE = 25;
|
|
99
|
+
/** How quickly the film's reading of the scroll's speed follows it (per second). */
|
|
100
|
+
const FILM_SPEED_RATE = 30;
|
|
101
|
+
/** A step this short is the same frame asked twice: it moves nothing and reads no speed. */
|
|
102
|
+
const FILM_SAME_FRAME_SECONDS = 0.002;
|
|
103
|
+
const FILM_MAX_STEP_SECONDS = 0.1;
|
|
104
|
+
const FILM_EPSILON = 1e-6;
|
|
92
105
|
/** How finely two neighbours mix. Finer than the eye reads, and a scroll that moves less than a step draws nothing. */
|
|
93
106
|
const FRAME_BLEND_STEPS = 32;
|
|
94
107
|
/**
|
|
@@ -105,6 +118,44 @@ function frameBlendAt(loaded, position) {
|
|
|
105
118
|
const nearest = nearestLoadedFrame(loaded, Math.round(position));
|
|
106
119
|
return nearest < 0 ? null : { from: nearest, to: nearest, mix: 0 };
|
|
107
120
|
}
|
|
121
|
+
/** Where a film stands before its first step: on the moment asked for, still. */
|
|
122
|
+
function filmAt(wanted) {
|
|
123
|
+
return { position: wanted, wanted, speed: 0, direction: 1 };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* One step of the film. Moving fast, it stands exactly where the scroll asks. Landing (slower than FILM_LANDING_SPEED
|
|
127
|
+
* pictures a second) it runs on at that speed to the whole picture at or past where the scroll will stop, and stands
|
|
128
|
+
* there: it never crawls through a dissolve, never stops between two pictures, and never steps back unless the scroll
|
|
129
|
+
* does. `aim` is where the scroll is heading, when the driver knows it (its undamped target); without it, it is guessed.
|
|
130
|
+
*/
|
|
131
|
+
function stepFilm(motion, wanted, seconds, last, aim) {
|
|
132
|
+
const sameFrame = seconds < FILM_SAME_FRAME_SECONDS;
|
|
133
|
+
const step = Math.min(FILM_MAX_STEP_SECONDS, seconds);
|
|
134
|
+
const moved = wanted - motion.wanted;
|
|
135
|
+
const speed = sameFrame ? motion.speed : motion.speed + (moved / step - motion.speed) * (1 - Math.exp(-FILM_SPEED_RATE * step));
|
|
136
|
+
const heading = aim === undefined ? moved : aim - wanted;
|
|
137
|
+
const direction = Math.abs(heading) > FILM_EPSILON ? (heading > 0 ? 1 : -1) : Math.abs(moved) > FILM_EPSILON ? (moved > 0 ? 1 : -1) : motion.direction;
|
|
138
|
+
// Moving: with the scroll, but never back over a picture a landing already reached unless the scroll turned.
|
|
139
|
+
if (Math.abs(speed) >= FILM_LANDING_SPEED) {
|
|
140
|
+
const position = direction > 0 ? Math.max(wanted, Math.min(motion.position, Math.ceil(wanted))) : Math.min(wanted, Math.max(motion.position, Math.floor(wanted)));
|
|
141
|
+
return { position, wanted, speed, direction };
|
|
142
|
+
}
|
|
143
|
+
if (sameFrame)
|
|
144
|
+
return { ...motion, wanted, direction };
|
|
145
|
+
const clamp = (value) => Math.min(last, Math.max(0, value));
|
|
146
|
+
const stop = aim ?? wanted + speed / FILM_LANDING_DECAY;
|
|
147
|
+
const reached = direction > 0 ? Math.ceil(motion.position - FILM_EPSILON) : Math.floor(motion.position + FILM_EPSILON);
|
|
148
|
+
const goal = clamp(direction > 0 ? Math.max(Math.ceil(stop - FILM_EPSILON), reached) : Math.min(Math.floor(stop + FILM_EPSILON), reached));
|
|
149
|
+
const behind = (wanted - motion.position) * direction;
|
|
150
|
+
const reach = Math.max(FILM_LANDING_SPEED * step, behind > 0 ? behind * (1 - Math.exp(-FILM_CATCH_RATE * step)) : 0);
|
|
151
|
+
const position = direction > 0 ? Math.min(goal, motion.position + reach) : Math.max(goal, motion.position - reach);
|
|
152
|
+
return { position, wanted, speed, direction };
|
|
153
|
+
}
|
|
154
|
+
/** Whether a film has landed: still, on the whole picture at or just past where the scroll stands. */
|
|
155
|
+
function filmLanded(motion) {
|
|
156
|
+
const past = (motion.position - motion.wanted) * motion.direction;
|
|
157
|
+
return Math.abs(motion.speed) < 0.01 && Number.isInteger(motion.position) && past > -FILM_EPSILON && past < 1;
|
|
158
|
+
}
|
|
108
159
|
/** The picture at `played` % of a set of `count`. */
|
|
109
160
|
function frameIndexAt(played, count) {
|
|
110
161
|
if (count <= 1)
|
|
@@ -199,12 +250,12 @@ class FramePlayer {
|
|
|
199
250
|
this.el = el;
|
|
200
251
|
this.sequence = sequence;
|
|
201
252
|
this.played = null;
|
|
202
|
-
this.
|
|
203
|
-
this.
|
|
253
|
+
this.motion = null;
|
|
254
|
+
this.steppedAt = 0;
|
|
255
|
+
this.landingFrame = null;
|
|
204
256
|
this.observer = null;
|
|
205
257
|
this.canvas = null;
|
|
206
258
|
this.context = null;
|
|
207
|
-
this.position = 0;
|
|
208
259
|
this.drawn = null;
|
|
209
260
|
this.nextToLoad = 0;
|
|
210
261
|
this.loading = 0;
|
|
@@ -230,39 +281,36 @@ class FramePlayer {
|
|
|
230
281
|
}, { rootMargin: NEAR_MARGIN });
|
|
231
282
|
this.observer.observe(options.near ?? el);
|
|
232
283
|
}
|
|
233
|
-
/**
|
|
234
|
-
|
|
235
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Stand at `played` %, on the way to `aim` % when the caller knows where the scroll is going. `now` is the caller's
|
|
286
|
+
* frame time: the player keeps time by frames, so a frame asked twice moves once however long the page took.
|
|
287
|
+
*/
|
|
288
|
+
seek(played, aim, now) {
|
|
289
|
+
if (played === this.played && aim === this.aim)
|
|
236
290
|
return;
|
|
237
291
|
this.played = played;
|
|
238
|
-
this.
|
|
239
|
-
this.
|
|
292
|
+
this.aim = aim;
|
|
293
|
+
this.step(now ?? this.host.performance?.now() ?? 0);
|
|
294
|
+
}
|
|
295
|
+
/** One step toward the moment asked for, drawn now; a landing goes on by itself until it stands on a whole picture. */
|
|
296
|
+
step(now) {
|
|
297
|
+
if (this.destroyed || this.played === null)
|
|
298
|
+
return;
|
|
299
|
+
const wanted = framePosition(this.played, this.info.count);
|
|
300
|
+
const aim = this.aim === undefined ? undefined : framePosition(this.aim, this.info.count);
|
|
301
|
+
this.motion = this.motion ? stepFilm(this.motion, wanted, (now - this.steppedAt) / 1000, this.info.count - 1, aim) : filmAt(wanted);
|
|
302
|
+
this.steppedAt = now;
|
|
240
303
|
this.draw();
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const target = Math.round(this.position);
|
|
247
|
-
const step = () => {
|
|
248
|
-
this.restFrame = null;
|
|
249
|
-
if (this.destroyed)
|
|
250
|
-
return;
|
|
251
|
-
const left = target - this.position;
|
|
252
|
-
this.position = Math.abs(left) < 1 / FRAME_BLEND_STEPS ? target : this.position + left * REST_RATE;
|
|
253
|
-
this.draw();
|
|
254
|
-
if (this.position !== target)
|
|
255
|
-
this.restFrame = this.host.requestAnimationFrame(step);
|
|
256
|
-
};
|
|
257
|
-
step();
|
|
304
|
+
if (!filmLanded(this.motion) && this.landingFrame === null)
|
|
305
|
+
this.landingFrame = this.host.requestAnimationFrame(time => {
|
|
306
|
+
this.landingFrame = null;
|
|
307
|
+
this.step(time);
|
|
308
|
+
});
|
|
258
309
|
}
|
|
259
|
-
|
|
260
|
-
if (this.
|
|
261
|
-
this.host.
|
|
262
|
-
|
|
263
|
-
this.host.cancelAnimationFrame?.(this.restFrame);
|
|
264
|
-
this.restTimer = null;
|
|
265
|
-
this.restFrame = null;
|
|
310
|
+
stopLanding() {
|
|
311
|
+
if (this.landingFrame !== null)
|
|
312
|
+
this.host.cancelAnimationFrame?.(this.landingFrame);
|
|
313
|
+
this.landingFrame = null;
|
|
266
314
|
}
|
|
267
315
|
/** Whether a re-render took the canvas away with the markup it sat in. */
|
|
268
316
|
get detached() {
|
|
@@ -270,7 +318,7 @@ class FramePlayer {
|
|
|
270
318
|
}
|
|
271
319
|
destroy() {
|
|
272
320
|
this.destroyed = true;
|
|
273
|
-
this.
|
|
321
|
+
this.stopLanding();
|
|
274
322
|
this.el.removeAttribute(FRAMES_SHOWING_ATTRIBUTE);
|
|
275
323
|
this.observer?.disconnect();
|
|
276
324
|
this.observer = null;
|
|
@@ -330,7 +378,9 @@ class FramePlayer {
|
|
|
330
378
|
image.src = frameUrl(this.sequence, this.set, index);
|
|
331
379
|
}
|
|
332
380
|
draw() {
|
|
333
|
-
|
|
381
|
+
if (!this.motion)
|
|
382
|
+
return;
|
|
383
|
+
const blend = frameBlendAt(this.images, this.motion.position);
|
|
334
384
|
const last = this.drawn;
|
|
335
385
|
if (!blend || (last && last.from === blend.from && last.to === blend.to && last.mix === blend.mix))
|
|
336
386
|
return;
|
|
@@ -988,5 +1038,5 @@ function videoRuntimeOf(el) {
|
|
|
988
1038
|
* Generated bundle index. Do not edit.
|
|
989
1039
|
*/
|
|
990
1040
|
|
|
991
|
-
export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, FRAMES_PHONE_MAX_WIDTH, FRAMES_SHOWING_ATTRIBUTE, FRAME_BLEND_STEPS, FRAME_SEQUENCE_ID, FrameExtension, FramePlayer, FrameSet, FramesClass, MAX_FRAMES, MAX_FRAME_BYTES, PHONE_LONG_SIDE, VIDEO_EMBED_ORIGINS, VIDEO_NODE_TYPE, VIDEO_PROVIDER_COMPANY, VIDEO_PROVIDER_LABEL, VIDEO_WORDS, VideoAttribute, VideoClass, VideoIcon, VideoKind, VideoMode, VideoRuntime, VideoSource, VideoTrait, WIDE_LONG_SIDE, attachVideoAt, attachVideos, documentConsentTransport, fittedFrameSize, frameBlendAt, frameFileName, frameIndexAt, frameLoadOrder, framePosition, frameSetFor, frameSummary, frameUrl, isThirdPartyPoster, nearestLoadedFrame, normalizeFrameSequence, parseVideoUrl, phoneFrameCount, phoneFrameSrcset, planVideo, readConsentRecord, sampledFrameIndexes, serializeVideoAttributes, sortFrameFiles, videoConsentCategory, videoEmbedUrl, videoFrameCount, videoInnerHtml, videoPlayLabel, videoPosterFileName, videoProp, videoRefOf, videoRootAttributes, videoRuntimeOf, videoSourceOf, videoThumbnailRequest, videoWords };
|
|
1041
|
+
export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT, FILM_LANDING_SPEED, FRAMES_PHONE_MAX_WIDTH, FRAMES_SHOWING_ATTRIBUTE, FRAME_BLEND_STEPS, FRAME_SEQUENCE_ID, FrameExtension, FramePlayer, FrameSet, FramesClass, MAX_FRAMES, MAX_FRAME_BYTES, PHONE_LONG_SIDE, VIDEO_EMBED_ORIGINS, VIDEO_NODE_TYPE, VIDEO_PROVIDER_COMPANY, VIDEO_PROVIDER_LABEL, VIDEO_WORDS, VideoAttribute, VideoClass, VideoIcon, VideoKind, VideoMode, VideoRuntime, VideoSource, VideoTrait, WIDE_LONG_SIDE, attachVideoAt, attachVideos, documentConsentTransport, filmAt, filmLanded, fittedFrameSize, frameBlendAt, frameFileName, frameIndexAt, frameLoadOrder, framePosition, frameSetFor, frameSummary, frameUrl, isThirdPartyPoster, nearestLoadedFrame, normalizeFrameSequence, parseVideoUrl, phoneFrameCount, phoneFrameSrcset, planVideo, readConsentRecord, sampledFrameIndexes, serializeVideoAttributes, sortFrameFiles, stepFilm, videoConsentCategory, videoEmbedUrl, videoFrameCount, videoInnerHtml, videoPlayLabel, videoPosterFileName, videoProp, videoRefOf, videoRootAttributes, videoRuntimeOf, videoSourceOf, videoThumbnailRequest, videoWords };
|
|
992
1042
|
//# sourceMappingURL=vectoriox-iox-ui-media-core.mjs.map
|