@vectoriox/iox-ui 5.75.2 → 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.
@@ -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,44 +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 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
- }
159
105
  /** The picture at `played` % of a set of `count`. */
160
106
  function frameIndexAt(played, count) {
161
107
  if (count <= 1)
@@ -250,9 +196,6 @@ class FramePlayer {
250
196
  this.el = el;
251
197
  this.sequence = sequence;
252
198
  this.played = null;
253
- this.motion = null;
254
- this.steppedAt = 0;
255
- this.landingFrame = null;
256
199
  this.observer = null;
257
200
  this.canvas = null;
258
201
  this.context = null;
@@ -282,35 +225,14 @@ class FramePlayer {
282
225
  this.observer.observe(options.near ?? el);
283
226
  }
284
227
  /**
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.
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.
287
230
  */
288
- seek(played, aim, now) {
289
- if (played === this.played && aim === this.aim)
231
+ seek(played) {
232
+ if (played === this.played)
290
233
  return;
291
234
  this.played = played;
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;
303
235
  this.draw();
304
- if (!filmLanded(this.motion) && this.landingFrame === null)
305
- this.landingFrame = this.host.requestAnimationFrame(time => {
306
- this.landingFrame = null;
307
- this.step(time);
308
- });
309
- }
310
- stopLanding() {
311
- if (this.landingFrame !== null)
312
- this.host.cancelAnimationFrame?.(this.landingFrame);
313
- this.landingFrame = null;
314
236
  }
315
237
  /** Whether a re-render took the canvas away with the markup it sat in. */
316
238
  get detached() {
@@ -318,7 +240,6 @@ class FramePlayer {
318
240
  }
319
241
  destroy() {
320
242
  this.destroyed = true;
321
- this.stopLanding();
322
243
  this.el.removeAttribute(FRAMES_SHOWING_ATTRIBUTE);
323
244
  this.observer?.disconnect();
324
245
  this.observer = null;
@@ -378,9 +299,9 @@ class FramePlayer {
378
299
  image.src = frameUrl(this.sequence, this.set, index);
379
300
  }
380
301
  draw() {
381
- if (!this.motion)
302
+ if (this.destroyed || this.played === null)
382
303
  return;
383
- const blend = frameBlendAt(this.images, this.motion.position);
304
+ const blend = frameBlendAt(this.images, framePosition(this.played, this.info.count));
384
305
  const last = this.drawn;
385
306
  if (!blend || (last && last.from === blend.from && last.to === blend.to && last.mix === blend.mix))
386
307
  return;
@@ -390,11 +311,20 @@ class FramePlayer {
390
311
  const context = this.context;
391
312
  const { width, height } = this.canvas;
392
313
  context.clearRect(0, 0, width, height);
393
- context.drawImage(image, 0, 0, width, height);
394
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);
395
321
  context.globalAlpha = blend.mix;
396
322
  context.drawImage(this.images[blend.to], 0, 0, width, height);
397
323
  context.globalAlpha = 1;
324
+ context.globalCompositeOperation = 'source-over';
325
+ }
326
+ else {
327
+ context.drawImage(image, 0, 0, width, height);
398
328
  }
399
329
  // Over a transparent film the poster would show through: a second, stiller subject behind the moving one.
400
330
  if (!this.drawn)
@@ -1038,5 +968,5 @@ function videoRuntimeOf(el) {
1038
968
  * Generated bundle index. Do not edit.
1039
969
  */
1040
970
 
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 };
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 };
1042
972
  //# sourceMappingURL=vectoriox-iox-ui-media-core.mjs.map