fix: decode portrait frames before first interaction
This commit is contained in:
@@ -838,6 +838,7 @@ var SkinPresenter = class {
|
|||||||
poster;
|
poster;
|
||||||
portrait;
|
portrait;
|
||||||
preloads;
|
preloads;
|
||||||
|
portraitReady = false;
|
||||||
enabled = true;
|
enabled = true;
|
||||||
// Default to the max frame so the first paint after load is the dark shell;
|
// Default to the max frame so the first paint after load is the dark shell;
|
||||||
// starting at 0 flashed the light palette before the directory resolved.
|
// starting at 0 flashed the light palette before the directory resolved.
|
||||||
@@ -852,7 +853,7 @@ var SkinPresenter = class {
|
|||||||
this.root = document.createElement("div");
|
this.root = document.createElement("div");
|
||||||
this.root.className = "liang-skin-backdrop";
|
this.root.className = "liang-skin-backdrop";
|
||||||
this.root.dataset.plugin = PACKAGE_ID;
|
this.root.dataset.plugin = PACKAGE_ID;
|
||||||
this.root.dataset.media = "sequence";
|
this.root.dataset.media = "poster";
|
||||||
this.root.setAttribute("aria-hidden", "true");
|
this.root.setAttribute("aria-hidden", "true");
|
||||||
this.poster = document.createElement("img");
|
this.poster = document.createElement("img");
|
||||||
this.poster.className = "liang-skin-poster";
|
this.poster.className = "liang-skin-poster";
|
||||||
@@ -866,10 +867,22 @@ var SkinPresenter = class {
|
|||||||
this.portrait.addEventListener("error", this.handleSequenceError);
|
this.portrait.addEventListener("error", this.handleSequenceError);
|
||||||
this.preloads = PORTRAIT_ANCHORS.map(({ file }) => {
|
this.preloads = PORTRAIT_ANCHORS.map(({ file }) => {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
image.loading = "eager";
|
||||||
image.decoding = "async";
|
image.decoding = "async";
|
||||||
image.src = `${ASSET_PREFIX}/portrait-source-v2/${file}`;
|
image.src = `${ASSET_PREFIX}/portrait-source-v2/${file}`;
|
||||||
return image;
|
return image;
|
||||||
});
|
});
|
||||||
|
void Promise.all(this.preloads.map((image) => image.decode())).then(
|
||||||
|
() => {
|
||||||
|
if (this.disposed) return;
|
||||||
|
this.portraitReady = true;
|
||||||
|
this.root.dataset.media = "sequence";
|
||||||
|
this.updatePortrait(paletteForFrame(this.frame).level);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (!this.disposed) this.root.dataset.media = "poster";
|
||||||
|
}
|
||||||
|
);
|
||||||
this.root.append(this.portrait, this.poster);
|
this.root.append(this.portrait, this.poster);
|
||||||
document.body.prepend(this.root);
|
document.body.prepend(this.root);
|
||||||
this.poster.addEventListener("error", this.handlePosterError);
|
this.poster.addEventListener("error", this.handlePosterError);
|
||||||
@@ -942,6 +955,7 @@ var SkinPresenter = class {
|
|||||||
this.theme.setTheme(theme);
|
this.theme.setTheme(theme);
|
||||||
}
|
}
|
||||||
updatePortrait(level) {
|
updatePortrait(level) {
|
||||||
|
if (!this.portraitReady) return;
|
||||||
const { lowerIndex, upperIndex, mix: mix2 } = portraitBlendForLevel(
|
const { lowerIndex, upperIndex, mix: mix2 } = portraitBlendForLevel(
|
||||||
level,
|
level,
|
||||||
ANCHOR_LEVELS
|
ANCHOR_LEVELS
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -143,6 +143,7 @@ class SkinPresenter {
|
|||||||
private readonly poster: HTMLImageElement;
|
private readonly poster: HTMLImageElement;
|
||||||
private readonly portrait: HTMLImageElement;
|
private readonly portrait: HTMLImageElement;
|
||||||
private readonly preloads: HTMLImageElement[];
|
private readonly preloads: HTMLImageElement[];
|
||||||
|
private portraitReady = false;
|
||||||
private enabled = true;
|
private enabled = true;
|
||||||
// Default to the max frame so the first paint after load is the dark shell;
|
// Default to the max frame so the first paint after load is the dark shell;
|
||||||
// starting at 0 flashed the light palette before the directory resolved.
|
// starting at 0 flashed the light palette before the directory resolved.
|
||||||
@@ -158,7 +159,10 @@ class SkinPresenter {
|
|||||||
this.root = document.createElement("div");
|
this.root = document.createElement("div");
|
||||||
this.root.className = "liang-skin-backdrop";
|
this.root.className = "liang-skin-backdrop";
|
||||||
this.root.dataset.plugin = PACKAGE_ID;
|
this.root.dataset.plugin = PACKAGE_ID;
|
||||||
this.root.dataset.media = "sequence";
|
// Keep the stable poster visible while every portrait frame is fetched
|
||||||
|
// and decoded. A request being complete does not mean the bitmap is ready
|
||||||
|
// for a tear-free first swap.
|
||||||
|
this.root.dataset.media = "poster";
|
||||||
this.root.setAttribute("aria-hidden", "true");
|
this.root.setAttribute("aria-hidden", "true");
|
||||||
|
|
||||||
this.poster = document.createElement("img");
|
this.poster = document.createElement("img");
|
||||||
@@ -175,11 +179,28 @@ class SkinPresenter {
|
|||||||
|
|
||||||
this.preloads = PORTRAIT_ANCHORS.map(({ file }) => {
|
this.preloads = PORTRAIT_ANCHORS.map(({ file }) => {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
image.loading = "eager";
|
||||||
image.decoding = "async";
|
image.decoding = "async";
|
||||||
image.src = `${ASSET_PREFIX}/portrait-source-v2/${file}`;
|
image.src = `${ASSET_PREFIX}/portrait-source-v2/${file}`;
|
||||||
return image;
|
return image;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// `new Image()` starts the requests, but the browser may still defer
|
||||||
|
// decoding until the image is attached to the document. Wait for all
|
||||||
|
// frames up front so the first slider interaction never reveals a blank
|
||||||
|
// or half-painted frame.
|
||||||
|
void Promise.all(this.preloads.map((image) => image.decode())).then(
|
||||||
|
() => {
|
||||||
|
if (this.disposed) return;
|
||||||
|
this.portraitReady = true;
|
||||||
|
this.root.dataset.media = "sequence";
|
||||||
|
this.updatePortrait(paletteForFrame(this.frame).level);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (!this.disposed) this.root.dataset.media = "poster";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.root.append(this.portrait, this.poster);
|
this.root.append(this.portrait, this.poster);
|
||||||
document.body.prepend(this.root);
|
document.body.prepend(this.root);
|
||||||
|
|
||||||
@@ -263,6 +284,7 @@ class SkinPresenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updatePortrait(level: number) {
|
private updatePortrait(level: number) {
|
||||||
|
if (!this.portraitReady) return;
|
||||||
const { lowerIndex, upperIndex, mix } = portraitBlendForLevel(
|
const { lowerIndex, upperIndex, mix } = portraitBlendForLevel(
|
||||||
level,
|
level,
|
||||||
ANCHOR_LEVELS,
|
ANCHOR_LEVELS,
|
||||||
|
|||||||
Reference in New Issue
Block a user