@vectoriox/iox-ui 5.75.3 → 5.76.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/fesm2022/vectoriox-iox-ui-media-core.mjs +17 -88
- package/fesm2022/vectoriox-iox-ui-media-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs +10 -8
- package/fesm2022/vectoriox-iox-ui-scene-core.mjs.map +1 -1
- package/media-core/index.d.ts +5 -40
- package/package.json +1 -1
- package/src/media-core/frames.ts +15 -102
- package/src/scene-core/scroll-driver.ts +10 -8
|
@@ -86,22 +86,6 @@ 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
|
-
* 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;
|
|
105
89
|
/** How finely two neighbours mix. Finer than the eye reads, and a scroll that moves less than a step draws nothing. */
|
|
106
90
|
const FRAME_BLEND_STEPS = 32;
|
|
107
91
|
/**
|
|
@@ -118,45 +102,6 @@ function frameBlendAt(loaded, position) {
|
|
|
118
102
|
const nearest = nearestLoadedFrame(loaded, Math.round(position));
|
|
119
103
|
return nearest < 0 ? null : { from: nearest, to: nearest, mix: 0 };
|
|
120
104
|
}
|
|
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 nearest where the scroll will stop, and stands there:
|
|
128
|
-
* it never crawls through a dissolve, never stops between two pictures, and never steps back over a picture it reached
|
|
129
|
-
* unless the scroll turns. `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
|
-
// The picture nearest the stop, unless reaching it would show a step back: then the next whole one on the way.
|
|
148
|
-
const nearest = Math.round(stop);
|
|
149
|
-
const back = (motion.position - nearest) * direction > 1 / FRAME_BLEND_STEPS;
|
|
150
|
-
const goal = clamp(back ? (direction > 0 ? Math.ceil(motion.position) : Math.floor(motion.position)) : nearest);
|
|
151
|
-
const behind = (wanted - motion.position) * direction;
|
|
152
|
-
const reach = Math.max(FILM_LANDING_SPEED * step, behind > 0 ? behind * (1 - Math.exp(-FILM_CATCH_RATE * step)) : 0);
|
|
153
|
-
const position = direction > 0 ? Math.min(goal, motion.position + reach) : Math.max(goal, motion.position - reach);
|
|
154
|
-
return { position, wanted, speed, direction };
|
|
155
|
-
}
|
|
156
|
-
/** Whether a film has landed: still, on a whole picture beside where the scroll stands. */
|
|
157
|
-
function filmLanded(motion) {
|
|
158
|
-
return Math.abs(motion.speed) < 0.01 && Number.isInteger(motion.position) && Math.abs(motion.position - motion.wanted) < 1;
|
|
159
|
-
}
|
|
160
105
|
/** The picture at `played` % of a set of `count`. */
|
|
161
106
|
function frameIndexAt(played, count) {
|
|
162
107
|
if (count <= 1)
|
|
@@ -251,9 +196,6 @@ class FramePlayer {
|
|
|
251
196
|
this.el = el;
|
|
252
197
|
this.sequence = sequence;
|
|
253
198
|
this.played = null;
|
|
254
|
-
this.motion = null;
|
|
255
|
-
this.steppedAt = 0;
|
|
256
|
-
this.landingFrame = null;
|
|
257
199
|
this.observer = null;
|
|
258
200
|
this.canvas = null;
|
|
259
201
|
this.context = null;
|
|
@@ -283,35 +225,14 @@ class FramePlayer {
|
|
|
283
225
|
this.observer.observe(options.near ?? el);
|
|
284
226
|
}
|
|
285
227
|
/**
|
|
286
|
-
* Stand at `played` %,
|
|
287
|
-
*
|
|
228
|
+
* Stand at `played` %, exactly. The film keeps no motion of its own: the scroll's glide, and the slowing at its end,
|
|
229
|
+
* are the caller's, so the film slows with the page and stops when it stops. Between two pictures it blends them.
|
|
288
230
|
*/
|
|
289
|
-
seek(played
|
|
290
|
-
if (played === this.played
|
|
231
|
+
seek(played) {
|
|
232
|
+
if (played === this.played)
|
|
291
233
|
return;
|
|
292
234
|
this.played = played;
|
|
293
|
-
this.aim = aim;
|
|
294
|
-
this.step(now ?? this.host.performance?.now() ?? 0);
|
|
295
|
-
}
|
|
296
|
-
/** One step toward the moment asked for, drawn now; a landing goes on by itself until it stands on a whole picture. */
|
|
297
|
-
step(now) {
|
|
298
|
-
if (this.destroyed || this.played === null)
|
|
299
|
-
return;
|
|
300
|
-
const wanted = framePosition(this.played, this.info.count);
|
|
301
|
-
const aim = this.aim === undefined ? undefined : framePosition(this.aim, this.info.count);
|
|
302
|
-
this.motion = this.motion ? stepFilm(this.motion, wanted, (now - this.steppedAt) / 1000, this.info.count - 1, aim) : filmAt(wanted);
|
|
303
|
-
this.steppedAt = now;
|
|
304
235
|
this.draw();
|
|
305
|
-
if (!filmLanded(this.motion) && this.landingFrame === null)
|
|
306
|
-
this.landingFrame = this.host.requestAnimationFrame(time => {
|
|
307
|
-
this.landingFrame = null;
|
|
308
|
-
this.step(time);
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
stopLanding() {
|
|
312
|
-
if (this.landingFrame !== null)
|
|
313
|
-
this.host.cancelAnimationFrame?.(this.landingFrame);
|
|
314
|
-
this.landingFrame = null;
|
|
315
236
|
}
|
|
316
237
|
/** Whether a re-render took the canvas away with the markup it sat in. */
|
|
317
238
|
get detached() {
|
|
@@ -319,7 +240,6 @@ class FramePlayer {
|
|
|
319
240
|
}
|
|
320
241
|
destroy() {
|
|
321
242
|
this.destroyed = true;
|
|
322
|
-
this.stopLanding();
|
|
323
243
|
this.el.removeAttribute(FRAMES_SHOWING_ATTRIBUTE);
|
|
324
244
|
this.observer?.disconnect();
|
|
325
245
|
this.observer = null;
|
|
@@ -379,9 +299,9 @@ class FramePlayer {
|
|
|
379
299
|
image.src = frameUrl(this.sequence, this.set, index);
|
|
380
300
|
}
|
|
381
301
|
draw() {
|
|
382
|
-
if (
|
|
302
|
+
if (this.destroyed || this.played === null)
|
|
383
303
|
return;
|
|
384
|
-
const blend = frameBlendAt(this.images, this.
|
|
304
|
+
const blend = frameBlendAt(this.images, framePosition(this.played, this.info.count));
|
|
385
305
|
const last = this.drawn;
|
|
386
306
|
if (!blend || (last && last.from === blend.from && last.to === blend.to && last.mix === blend.mix))
|
|
387
307
|
return;
|
|
@@ -391,11 +311,20 @@ class FramePlayer {
|
|
|
391
311
|
const context = this.context;
|
|
392
312
|
const { width, height } = this.canvas;
|
|
393
313
|
context.clearRect(0, 0, width, height);
|
|
394
|
-
context.drawImage(image, 0, 0, width, height);
|
|
395
314
|
if (blend.mix > 0) {
|
|
315
|
+
// A true mix, (1 − mix)·from + mix·to, even where a see-through film has nothing on one side. Drawing `to` over
|
|
316
|
+
// an opaque `from` kept `from`'s outline whole until the next picture, so the subject's edge jumped a picture at
|
|
317
|
+
// a time instead of moving.
|
|
318
|
+
context.globalCompositeOperation = 'lighter';
|
|
319
|
+
context.globalAlpha = 1 - blend.mix;
|
|
320
|
+
context.drawImage(image, 0, 0, width, height);
|
|
396
321
|
context.globalAlpha = blend.mix;
|
|
397
322
|
context.drawImage(this.images[blend.to], 0, 0, width, height);
|
|
398
323
|
context.globalAlpha = 1;
|
|
324
|
+
context.globalCompositeOperation = 'source-over';
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
context.drawImage(image, 0, 0, width, height);
|
|
399
328
|
}
|
|
400
329
|
// Over a transparent film the poster would show through: a second, stiller subject behind the moving one.
|
|
401
330
|
if (!this.drawn)
|
|
@@ -1039,5 +968,5 @@ function videoRuntimeOf(el) {
|
|
|
1039
968
|
* Generated bundle index. Do not edit.
|
|
1040
969
|
*/
|
|
1041
970
|
|
|
1042
|
-
export { CONSENT_GRANT_EVENT, CONSENT_PREFERENCES_EVENT,
|
|
971
|
+
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 };
|
|
1043
972
|
//# sourceMappingURL=vectoriox-iox-ui-media-core.mjs.map
|