refactor(common): allow string urls in NgOptimizedImage input (#54901)

With this commit it is now possible to pass an url as placeholder for the image directive.

PR Close #54901
This commit is contained in:
Matthieu Riegler
2024-04-01 13:08:51 +02:00
committed by Jessica Janiuk
parent 341a116d61
commit f407e3ce6f
4 changed files with 105 additions and 4 deletions
@@ -117,6 +117,12 @@ const FIXED_SRCSET_HEIGHT_LIMIT = 1080;
*/
export const PLACEHOLDER_BLUR_AMOUNT = 15;
/**
* Placeholder dimension (height or width) limit in pixels. Angular produces a warning
* when this limit is crossed.
*/
const PLACEHOLDER_DIMENSION_LIMIT = 1000;
/**
* Used to warn or error when the user provides an overly large dataURL for the placeholder
* attribute.
@@ -364,7 +370,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
/**
* A URL or data URL for an image to be used as a placeholder while this image loads.
*/
@Input({transform: booleanOrDataUrlAttribute}) placeholder?: string | boolean;
@Input({transform: booleanOrUrlAttribute}) placeholder?: string | boolean;
/**
* Configuration object for placeholder settings. Options:
@@ -518,6 +524,10 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
});
}
}
if (ngDevMode && changes['placeholder']?.currentValue && !this.isServer) {
assertPlaceholderDimensions(this, this.imgElement);
}
}
private callImageLoader(
@@ -651,7 +661,7 @@ export class NgOptimizedImage implements OnInit, OnChanges, OnDestroy {
width: placeholderResolution,
isPlaceholder: true,
})})`;
} else if (typeof placeholderInput === 'string' && placeholderInput.startsWith('data:')) {
} else if (typeof placeholderInput === 'string') {
return `url(${placeholderInput})`;
}
return null;
@@ -1286,6 +1296,28 @@ function assetPriorityCountBelowThreshold() {
IMGS_WITH_PRIORITY_ATTR_COUNT++;
}
/**
* Warns if placeholder's dimension are over a threshold.
*
* This assert function is meant to only run on the browser.
*/
function assertPlaceholderDimensions(dir: NgOptimizedImage, imgElement: HTMLImageElement) {
const computedStyle = window.getComputedStyle(imgElement);
let renderedWidth = parseFloat(computedStyle.getPropertyValue('width'));
let renderedHeight = parseFloat(computedStyle.getPropertyValue('height'));
if (renderedWidth > PLACEHOLDER_DIMENSION_LIMIT || renderedHeight > PLACEHOLDER_DIMENSION_LIMIT) {
console.warn(
formatRuntimeError(
RuntimeErrorCode.PLACEHOLDER_DIMENSION_LIMIT_EXCEEDED,
`${imgDirectiveDetails(dir.ngSrc)} it uses a placeholder image, but at least one of ` +
`of the dimensions attribute (height or width) exceeds the limit of ${PLACEHOLDER_DIMENSION_LIMIT}px. ` +
`To fix this, use a smaller image as a placeholder.`,
),
);
}
}
function round(input: number): number | string {
return Number.isInteger(input) ? input : input.toFixed(2);
}
@@ -1301,8 +1333,8 @@ function unwrapSafeUrl(value: string | SafeValue): string {
// Transform function to handle inputs which may be booleans, strings, or string representations
// of boolean values. Used for the placeholder attribute.
export function booleanOrDataUrlAttribute(value: boolean | string): boolean | string {
if (typeof value === 'string' && value.startsWith(`data:`)) {
export function booleanOrUrlAttribute(value: boolean | string): boolean | string {
if (typeof value === 'string' && value !== 'true' && value !== 'false' && value !== '') {
return value;
}
return booleanAttribute(value);
+1
View File
@@ -40,4 +40,5 @@ export const enum RuntimeErrorCode {
LCP_IMG_NGSRC_MODIFIED = 2964,
OVERSIZED_PLACEHOLDER = 2965,
TOO_MANY_PRIORITY_ATTRIBUTES = 2966,
PLACEHOLDER_DIMENSION_LIMIT_EXCEEDED = 2967,
}