site-library/packages/imagetools/api/renderBackgroundImage.js
2025-03-08 21:04:49 +01:00

160 lines
3.9 KiB
JavaScript

// @ts-check
import crypto from "node:crypto";
import getImage from "./utils/getImage.js";
import getLinkElement from "./utils/getLinkElement.js";
import getStyleElement from "./utils/getStyleElement.js";
import getFilteredProps from "./utils/getFilteredProps.js";
import getContainerElement from "./utils/getContainerElement.js";
export default async function renderBackgroundImage(props) {
const type = "BackgroundImage";
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
const {
src,
tag,
content,
preload,
attributes,
placeholder,
breakpoints,
backgroundSize,
backgroundPosition,
format,
fallbackFormat,
includeSourceFormat,
formatOptions,
artDirectives,
} = filteredProps;
const {
link: linkAttributes = {},
style: styleAttributes = {},
container: containerAttributes = {},
} = attributes;
const sizes = "";
const { uuid, images } = await getImage({
src,
type,
sizes,
format,
breakpoints,
placeholder,
artDirectives,
fallbackFormat,
includeSourceFormat,
formatOptions,
transformConfigs,
});
const className = `astro-imagetools-background-image-${uuid}`;
const { imagesizes } = images[images.length - 1];
const link = getLinkElement({ images, preload, imagesizes, linkAttributes });
const backgroundImageStylesArray = images.map(({ media, sources }) => {
const uuid = crypto.randomBytes(4).toString("hex").toUpperCase();
const fallbackUrlCustomVariable = `--astro-imagetools-background-image-fallback-url${uuid}`;
const newSources = {};
sources.forEach(({ src, format, srcset }) => {
const sources = srcset
.split(", ")
.map((source) => [
source.slice(0, source.lastIndexOf(" ")),
source.slice(source.lastIndexOf(" ") + 1, -1),
]);
sources.forEach(([path, width]) => {
if (!newSources[width]) {
newSources[width] = [];
}
newSources[width].push({ src, format, path });
});
});
const widths = Object.keys(newSources)
.map((width) => parseInt(width))
.reverse();
const maxWidth = Math.max(...widths);
const styles = widths
.map((width) => {
const sources = newSources[width];
const styles = sources
.map(
({ format, path }, i) =>
`
${i !== sources.length - 1 ? `.${format} ` : ""}.${className} {
background-repeat: no-repeat;
background-image: url(${path}),
var(${fallbackUrlCustomVariable});
background-size: ${backgroundSize};
background-position: ${backgroundPosition};
}
`
)
.reverse()
.join("");
return width === maxWidth
? styles
: `
@media screen and (max-width: ${width}px) {
${styles}
}
`;
})
.join("");
return {
fallbackUrlCustomVariable,
styles: media
? `
@media ${media} {
${styles}
}
`
: styles,
};
});
const containerStyles = `
.${className} {
position: relative;
${images
.map(({ fallback }, i) => {
const fallbackUrlCustomVariable =
backgroundImageStylesArray[i].fallbackUrlCustomVariable;
return `${fallbackUrlCustomVariable}: url("${encodeURI(fallback)}");`;
})
.join("\n")}
}
`;
const backgroundStyles =
backgroundImageStylesArray.map(({ styles }) => styles).join("\n") +
containerStyles;
const style = getStyleElement({ styleAttributes, backgroundStyles });
const htmlElement = getContainerElement({
tag,
content,
className,
containerAttributes,
});
return { link, style, htmlElement };
}