diff --git a/src/core/dom-renderer/domRenderer.ts b/src/core/dom-renderer/domRenderer.ts index 41416cf..cf311a9 100644 --- a/src/core/dom-renderer/domRenderer.ts +++ b/src/core/dom-renderer/domRenderer.ts @@ -857,6 +857,22 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.imgEl.addEventListener('error', () => { node.imageLoading = false; + + const failedSrc = + node.imgEl?.dataset.pendingSrc || node.lazyImagePendingSrc || ''; + + const fallback = node.props.fallbackImage; + if ( + fallback && + node.imgEl && + node.imgEl.dataset.rawSrc !== fallback + ) { + node.imgEl.dataset.pendingSrc = fallback; + node.imgEl.dataset.rawSrc = fallback; + node.imgEl.src = fallback; + return; + } + node.showBackgroundLayer(); if (node.imgEl) { node.imgEl.removeAttribute('src'); @@ -864,9 +880,6 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.imgEl.removeAttribute('data-rawSrc'); } - const failedSrc = - node.imgEl?.dataset.pendingSrc || node.lazyImagePendingSrc || ''; - const payload: lng.NodeTextureFailedPayload = { type: 'texture', error: new Error(`Failed to load image: ${failedSrc}`), @@ -977,15 +990,28 @@ function updateNodeStyles(node: DOMNode | DOMText) { node.imgEl.addEventListener('error', () => { node.imageLoading = false; + + const failedSrc = + node.imgEl?.dataset.pendingSrc || node.lazyImagePendingSrc || ''; + + const fallback = node.props.fallbackImage; + if ( + fallback && + node.imgEl && + node.imgEl.dataset.rawSrc !== fallback + ) { + node.imgEl.dataset.pendingSrc = fallback; + node.imgEl.dataset.rawSrc = fallback; + node.imgEl.src = fallback; + return; + } + if (node.imgEl) { node.imgEl.removeAttribute('src'); node.imgEl.style.display = 'none'; node.imgEl.removeAttribute('data-rawSrc'); } - const failedSrc = - node.imgEl?.dataset.pendingSrc || node.lazyImagePendingSrc || ''; - const payload: lng.NodeTextureFailedPayload = { type: 'texture', error: new Error(`Failed to load image: ${failedSrc}`), @@ -1285,6 +1311,7 @@ function resolveNodeDefaults( rotation: props.rotation ?? 0, rtt: props.rtt ?? false, placeholderColor: props.placeholderColor ?? 0, + fallbackImage: props.fallbackImage ?? null, data: {}, imageType: props.imageType, }; @@ -1829,6 +1856,15 @@ export class DOMNode extends EventEmitter implements IRendererNode { updateNodeStyles(this); } + get fallbackImage(): string | null { + return this.props.fallbackImage ?? null; + } + + set fallbackImage(v: string | null) { + if (this.props.fallbackImage === v) return; + this.props.fallbackImage = v; + } + get absX(): number { const parent = this.props.parent; return ( diff --git a/src/core/elementNode.ts b/src/core/elementNode.ts index 5408753..a02ac24 100644 --- a/src/core/elementNode.ts +++ b/src/core/elementNode.ts @@ -276,6 +276,7 @@ const LightningRendererNonAnimatingProps = [ 'offsetY', 'overflowSuffix', 'placeholderColor', + 'fallbackImage', 'preventCleanup', 'rtt', 'scrollable', @@ -793,6 +794,7 @@ export class ElementNode { text: undefined, ignoreParentAlpha: undefined, placeholderColor: undefined, + fallbackImage: undefined, }; this.children = [];