2
0

fix: use portrait frame as loading fallback

This commit is contained in:
kingOfSoySauce
2026-08-21 11:22:40 +08:00
parent d2d605a0bd
commit 976fcbf9b4
7 changed files with 29 additions and 61 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

View File

@@ -62,23 +62,11 @@ body[data-liang-skin="on"] .liang-skin-backdrop {
display: none; display: none;
} }
.liang-skin-backdrop[data-media="sequence"] .liang-skin-poster {
display: none;
}
.liang-skin-backdrop[data-media="sequence"] .liang-skin-sequence-frame { .liang-skin-backdrop[data-media="sequence"] .liang-skin-sequence-frame {
display: block; display: block;
opacity: var(--liang-portrait-opacity, 0.82); opacity: var(--liang-portrait-opacity, 0.82);
} }
.liang-skin-backdrop[data-media="poster"] .liang-skin-sequence-frame {
display: none;
}
.liang-skin-backdrop[data-media="poster"] .liang-skin-poster {
display: block;
}
.liang-skin-backdrop[data-media="color"] img { .liang-skin-backdrop[data-media="color"] img {
display: none; display: none;
} }
@@ -786,6 +774,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
var PACKAGE_ID = "dsh-client-liang-intensity-skin"; var PACKAGE_ID = "dsh-client-liang-intensity-skin";
var LOCALE_NAMESPACE = "liang.skin"; var LOCALE_NAMESPACE = "liang.skin";
var ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets`; var ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets`;
var FIRST_PORTRAIT_FILE = "portrait-source-v2/stage-00.webp";
var BIND_EFFORT_KEY = "dsh-liang-intensity-skin.bind-effort"; var BIND_EFFORT_KEY = "dsh-liang-intensity-skin.bind-effort";
var PORTRAIT_ANCHORS = [ var PORTRAIT_ANCHORS = [
{ level: 0, file: "stage-00.webp" }, { level: 0, file: "stage-00.webp" },
@@ -835,7 +824,6 @@ var SkinPresenter = class {
scope; scope;
theme; theme;
root; root;
poster;
portrait; portrait;
preloads; preloads;
portraitReady = false; portraitReady = false;
@@ -853,17 +841,14 @@ 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 = "poster"; this.root.dataset.media = "sequence";
this.root.setAttribute("aria-hidden", "true"); this.root.setAttribute("aria-hidden", "true");
this.poster = document.createElement("img");
this.poster.className = "liang-skin-poster";
this.poster.src = `${ASSET_PREFIX}/liang-poster.webp`;
this.poster.alt = "";
this.portrait = document.createElement("img"); this.portrait = document.createElement("img");
this.portrait.className = "liang-skin-sequence-frame"; this.portrait.className = "liang-skin-sequence-frame";
this.portrait.alt = ""; this.portrait.alt = "";
this.portrait.draggable = false; this.portrait.draggable = false;
this.portrait.decoding = "async"; this.portrait.decoding = "async";
this.portrait.src = `${ASSET_PREFIX}/${FIRST_PORTRAIT_FILE}`;
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();
@@ -880,20 +865,21 @@ var SkinPresenter = class {
this.updatePortrait(paletteForFrame(this.frame).level); 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);
document.body.prepend(this.root); document.body.prepend(this.root);
this.poster.addEventListener("error", this.handlePosterError);
this.unsubscribe = scope.subscribe(() => this.syncSettings()); this.unsubscribe = scope.subscribe(() => this.syncSettings());
this.syncSettings(); this.syncSettings();
} }
handleSequenceError = () => { handleSequenceError = () => {
this.root.dataset.media = "poster"; if (this.portrait.src.endsWith(FIRST_PORTRAIT_FILE)) {
};
handlePosterError = () => {
this.root.dataset.media = "color"; this.root.dataset.media = "color";
return;
}
this.portraitReady = false;
this.portrait.src = `${ASSET_PREFIX}/${FIRST_PORTRAIT_FILE}`;
this.root.dataset.media = "sequence";
}; };
syncSettings() { syncSettings() {
this.setEnabled(this.scope.getSnapshot().enabled); this.setEnabled(this.scope.getSnapshot().enabled);
@@ -981,7 +967,6 @@ var SkinPresenter = class {
this.unsubscribe(); this.unsubscribe();
if (this.raf !== 0) cancelAnimationFrame(this.raf); if (this.raf !== 0) cancelAnimationFrame(this.raf);
this.portrait.removeEventListener("error", this.handleSequenceError); this.portrait.removeEventListener("error", this.handleSequenceError);
this.poster.removeEventListener("error", this.handlePosterError);
for (const image of this.preloads) image.src = ""; for (const image of this.preloads) image.src = "";
this.root.remove(); this.root.remove();
document.body.removeAttribute("data-liang-skin"); document.body.removeAttribute("data-liang-skin");

File diff suppressed because one or more lines are too long

View File

@@ -17,7 +17,6 @@
"./package.json": "./package.json" "./package.json": "./package.json"
}, },
"files": [ "files": [
"assets/liang-poster.webp",
"assets/portrait-source-v2/stage-00.webp", "assets/portrait-source-v2/stage-00.webp",
"assets/portrait-source-v2/level-01.webp", "assets/portrait-source-v2/level-01.webp",
"assets/portrait-source-v2/level-03.webp", "assets/portrait-source-v2/level-03.webp",

View File

@@ -24,6 +24,7 @@ import {
const PACKAGE_ID = "dsh-client-liang-intensity-skin"; const PACKAGE_ID = "dsh-client-liang-intensity-skin";
const LOCALE_NAMESPACE = "liang.skin"; const LOCALE_NAMESPACE = "liang.skin";
const ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets`; const ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets`;
const FIRST_PORTRAIT_FILE = "portrait-source-v2/stage-00.webp";
const BIND_EFFORT_KEY = "dsh-liang-intensity-skin.bind-effort"; const BIND_EFFORT_KEY = "dsh-liang-intensity-skin.bind-effort";
const PORTRAIT_ANCHORS = [ const PORTRAIT_ANCHORS = [
@@ -140,7 +141,6 @@ class SkinPresenter {
private readonly scope: PreferenceStore; private readonly scope: PreferenceStore;
private readonly theme: ThemeService; private readonly theme: ThemeService;
private readonly root: HTMLDivElement; private readonly root: HTMLDivElement;
private readonly poster: HTMLImageElement;
private readonly portrait: HTMLImageElement; private readonly portrait: HTMLImageElement;
private readonly preloads: HTMLImageElement[]; private readonly preloads: HTMLImageElement[];
private portraitReady = false; private portraitReady = false;
@@ -159,22 +159,18 @@ 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;
// Keep the stable poster visible while every portrait frame is fetched // Show the first half-body frame immediately while the remaining frames
// and decoded. A request being complete does not mean the bitmap is ready // are fetched and decoded. A request being complete does not mean the
// for a tear-free first swap. // bitmap is ready for a tear-free first swap.
this.root.dataset.media = "poster"; this.root.dataset.media = "sequence";
this.root.setAttribute("aria-hidden", "true"); this.root.setAttribute("aria-hidden", "true");
this.poster = document.createElement("img");
this.poster.className = "liang-skin-poster";
this.poster.src = `${ASSET_PREFIX}/liang-poster.webp`;
this.poster.alt = "";
this.portrait = document.createElement("img"); this.portrait = document.createElement("img");
this.portrait.className = "liang-skin-sequence-frame"; this.portrait.className = "liang-skin-sequence-frame";
this.portrait.alt = ""; this.portrait.alt = "";
this.portrait.draggable = false; this.portrait.draggable = false;
this.portrait.decoding = "async"; this.portrait.decoding = "async";
this.portrait.src = `${ASSET_PREFIX}/${FIRST_PORTRAIT_FILE}`;
this.portrait.addEventListener("error", this.handleSequenceError); this.portrait.addEventListener("error", this.handleSequenceError);
this.preloads = PORTRAIT_ANCHORS.map(({ file }) => { this.preloads = PORTRAIT_ANCHORS.map(({ file }) => {
@@ -197,24 +193,26 @@ class SkinPresenter {
this.updatePortrait(paletteForFrame(this.frame).level); this.updatePortrait(paletteForFrame(this.frame).level);
}, },
() => { () => {
if (!this.disposed) this.root.dataset.media = "poster"; // Keep the already-visible first half-body frame if another optional
// frame cannot be decoded. It is still a valid skin fallback.
}, },
); );
this.root.append(this.portrait, this.poster); this.root.append(this.portrait);
document.body.prepend(this.root); document.body.prepend(this.root);
this.poster.addEventListener("error", this.handlePosterError);
this.unsubscribe = scope.subscribe(() => this.syncSettings()); this.unsubscribe = scope.subscribe(() => this.syncSettings());
this.syncSettings(); this.syncSettings();
} }
private readonly handleSequenceError = () => { private readonly handleSequenceError = () => {
this.root.dataset.media = "poster"; if (this.portrait.src.endsWith(FIRST_PORTRAIT_FILE)) {
};
private readonly handlePosterError = () => {
this.root.dataset.media = "color"; this.root.dataset.media = "color";
return;
}
this.portraitReady = false;
this.portrait.src = `${ASSET_PREFIX}/${FIRST_PORTRAIT_FILE}`;
this.root.dataset.media = "sequence";
}; };
private syncSettings() { private syncSettings() {
@@ -312,7 +310,6 @@ class SkinPresenter {
this.unsubscribe(); this.unsubscribe();
if (this.raf !== 0) cancelAnimationFrame(this.raf); if (this.raf !== 0) cancelAnimationFrame(this.raf);
this.portrait.removeEventListener("error", this.handleSequenceError); this.portrait.removeEventListener("error", this.handleSequenceError);
this.poster.removeEventListener("error", this.handlePosterError);
for (const image of this.preloads) image.src = ""; for (const image of this.preloads) image.src = "";
this.root.remove(); this.root.remove();
document.body.removeAttribute("data-liang-skin"); document.body.removeAttribute("data-liang-skin");

View File

@@ -33,23 +33,11 @@ body[data-liang-skin="on"] .liang-skin-backdrop {
display: none; display: none;
} }
.liang-skin-backdrop[data-media="sequence"] .liang-skin-poster {
display: none;
}
.liang-skin-backdrop[data-media="sequence"] .liang-skin-sequence-frame { .liang-skin-backdrop[data-media="sequence"] .liang-skin-sequence-frame {
display: block; display: block;
opacity: var(--liang-portrait-opacity, 0.82); opacity: var(--liang-portrait-opacity, 0.82);
} }
.liang-skin-backdrop[data-media="poster"] .liang-skin-sequence-frame {
display: none;
}
.liang-skin-backdrop[data-media="poster"] .liang-skin-poster {
display: block;
}
.liang-skin-backdrop[data-media="color"] img { .liang-skin-backdrop[data-media="color"] img {
display: none; display: none;
} }

View File

@@ -9,7 +9,6 @@ export const SETTINGS_NAMESPACE = "liang-intensity-skin";
export const ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets/`; export const ASSET_PREFIX = `/plugins/${PACKAGE_ID}/assets/`;
const ASSET_SPECS = [ const ASSET_SPECS = [
["liang-poster.webp", "image/webp"],
["portrait-source-v2/stage-00.webp", "image/webp"], ["portrait-source-v2/stage-00.webp", "image/webp"],
["portrait-source-v2/level-01.webp", "image/webp"], ["portrait-source-v2/level-01.webp", "image/webp"],
["portrait-source-v2/level-03.webp", "image/webp"], ["portrait-source-v2/level-03.webp", "image/webp"],