Skip to content

Commit 6b30259

Browse files
authored
Merge pull request #337 from 4site-interactive-studios/background-image-positioning
Added background image positioning support
2 parents 6bfdc03 + ab09ce4 commit 6b30259

6 files changed

Lines changed: 720 additions & 99 deletions

File tree

packages/scripts/dist/page-background.d.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,64 @@
11
export declare class PageBackground {
22
private pageBackground;
3+
private mutationObserver;
4+
private logger;
35
constructor();
6+
/**
7+
* Initialize background image by finding and setting CSS custom property
8+
*/
9+
private initializeBackgroundImage;
10+
/**
11+
* Set the background image URL as a CSS custom property
12+
*/
13+
private setBackgroundImageUrl;
14+
/**
15+
* Processes attribution positioning for background images by moving positioning classes
16+
* and data attributes from images to their parent column containers.
17+
*
18+
* This function handles two attribution patterns:
19+
* 1. Class-based: <img class="attribution-bottomright" src="...">
20+
* 2. Data attribute-based: <img data-background-position="bottomright" src="...">
21+
*
22+
* Examples:
23+
*
24+
* Class-based attribution:
25+
* <img class="attribution-bottomright" src="background.jpg">
26+
* → Moves "attribution-bottomright" class to parent .en__component--column
27+
*
28+
* Data attribute-based attribution:
29+
* <img data-background-position="top" src="background.jpg">
30+
* → Converts to "attribution-top" class and moves to parent .en__component--column
31+
*
32+
* Supported positioning values:
33+
* - center
34+
* - top, topcenter (these result in the same positioning)
35+
* - right, rightcenter (these result in the same positioning)
36+
* - bottom, bottomcenter (these result in the same positioning)
37+
* - left, leftcenter (these result in the same positioning)
38+
* - topright
39+
* - bottomright
40+
* - bottomleft
41+
* - topleft
42+
*/
43+
private processAttributionPositioning;
44+
/**
45+
* Process attribution for a single image
46+
*/
47+
private processImageAttribution;
48+
/**
49+
* Handle class-based attribution positioning
50+
*/
51+
private handleClassBasedAttribution;
52+
/**
53+
* Handle data attribute-based attribution positioning
54+
*/
55+
private handleDataAttributeAttribution;
56+
private setupMutationObserver;
57+
reprocessAttributionPositioning(): void;
58+
/**
59+
* Clean up resources and observers
60+
*/
61+
destroy(): void;
462
private setDataAttributes;
563
private hasVideoBackground;
664
private hasImageBackground;
Lines changed: 222 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,242 @@
1-
import { ENGrid } from "./engrid";
1+
import { ENGrid, EngridLogger } from ".";
22
export class PageBackground {
33
constructor() {
44
// @TODO: Change page-backgroundImage to page-background
55
this.pageBackground = document.querySelector(".page-backgroundImage");
6-
// Finds any <img> added to the "backgroundImage" ENGRid section and sets it as the "--engrid__page-backgroundImage_url" CSS Custom Property
7-
if (this.pageBackground) {
8-
const pageBackgroundImg = this.pageBackground.querySelector("img");
9-
let pageBackgroundImgDataSrc = pageBackgroundImg === null || pageBackgroundImg === void 0 ? void 0 : pageBackgroundImg.getAttribute("data-src");
10-
let pageBackgroundImgSrc = pageBackgroundImg === null || pageBackgroundImg === void 0 ? void 0 : pageBackgroundImg.src;
11-
if (this.pageBackground && pageBackgroundImgDataSrc) {
12-
if (ENGrid.debug)
13-
console.log("A background image set in the page was found with a data-src value, setting it as --engrid__page-backgroundImage_url", pageBackgroundImgDataSrc);
14-
pageBackgroundImgDataSrc = "url('" + pageBackgroundImgDataSrc + "')";
15-
this.pageBackground.style.setProperty("--engrid__page-backgroundImage_url", pageBackgroundImgDataSrc);
16-
}
17-
else if (this.pageBackground && pageBackgroundImgSrc) {
18-
if (ENGrid.debug)
19-
console.log("A background image set in the page was found with a src value, setting it as --engrid__page-backgroundImage_url", pageBackgroundImgSrc);
20-
pageBackgroundImgSrc = "url('" + pageBackgroundImgSrc + "')";
21-
this.pageBackground.style.setProperty("--engrid__page-backgroundImage_url", pageBackgroundImgSrc);
22-
}
23-
else if (pageBackgroundImg) {
24-
if (ENGrid.debug)
25-
console.log("A background image set in the page was found but without a data-src or src value, no action taken", pageBackgroundImg);
26-
}
27-
else {
28-
if (ENGrid.debug)
29-
console.log("A background image set in the page was not found, any default image set in the theme on --engrid__page-backgroundImage_url will be used");
30-
}
6+
this.mutationObserver = null;
7+
this.logger = new EngridLogger("PageBackground", "lightblue", "darkblue", "🖼️");
8+
if (!this.pageBackground) {
9+
this.logger.log("A background image set in the page was not found, any default image set in the theme on --engrid__page-backgroundImage_url will be used");
10+
return;
11+
}
12+
this.initializeBackgroundImage();
13+
this.setDataAttributes();
14+
this.processAttributionPositioning();
15+
this.setupMutationObserver();
16+
}
17+
/**
18+
* Initialize background image by finding and setting CSS custom property
19+
*/
20+
initializeBackgroundImage() {
21+
if (!this.pageBackground)
22+
return;
23+
const pageBackgroundImg = this.pageBackground.querySelector("img");
24+
if (!pageBackgroundImg) {
25+
this.logger.log("A background image set in the page was not found, any default image set in the theme on --engrid__page-backgroundImage_url will be used");
26+
return;
27+
}
28+
const dataSrc = pageBackgroundImg.getAttribute("data-src");
29+
const src = pageBackgroundImg.src;
30+
if (dataSrc) {
31+
this.setBackgroundImageUrl(dataSrc, "data-src");
32+
}
33+
else if (src) {
34+
this.setBackgroundImageUrl(src, "src");
3135
}
3236
else {
33-
if (ENGrid.debug)
34-
console.log("A background image set in the page was not found, any default image set in the theme on --engrid__page-backgroundImage_url will be used");
37+
this.logger.log("A background image set in the page was found but without a data-src or src value, no action taken", pageBackgroundImg);
38+
}
39+
}
40+
/**
41+
* Set the background image URL as a CSS custom property
42+
*/
43+
setBackgroundImageUrl(imageUrl, sourceType) {
44+
if (!this.pageBackground || !imageUrl)
45+
return;
46+
try {
47+
const cssUrl = `url('${imageUrl}')`;
48+
this.pageBackground.style.setProperty("--engrid__page-backgroundImage_url", cssUrl);
49+
this.logger.log(`A background image set in the page was found with a ${sourceType} value, setting it as --engrid__page-backgroundImage_url`, imageUrl);
50+
}
51+
catch (error) {
52+
this.logger.error("Error setting background image URL:", error);
53+
}
54+
}
55+
/**
56+
* Processes attribution positioning for background images by moving positioning classes
57+
* and data attributes from images to their parent column containers.
58+
*
59+
* This function handles two attribution patterns:
60+
* 1. Class-based: <img class="attribution-bottomright" src="...">
61+
* 2. Data attribute-based: <img data-background-position="bottomright" src="...">
62+
*
63+
* Examples:
64+
*
65+
* Class-based attribution:
66+
* <img class="attribution-bottomright" src="background.jpg">
67+
* → Moves "attribution-bottomright" class to parent .en__component--column
68+
*
69+
* Data attribute-based attribution:
70+
* <img data-background-position="top" src="background.jpg">
71+
* → Converts to "attribution-top" class and moves to parent .en__component--column
72+
*
73+
* Supported positioning values:
74+
* - center
75+
* - top, topcenter (these result in the same positioning)
76+
* - right, rightcenter (these result in the same positioning)
77+
* - bottom, bottomcenter (these result in the same positioning)
78+
* - left, leftcenter (these result in the same positioning)
79+
* - topright
80+
* - bottomright
81+
* - bottomleft
82+
* - topleft
83+
*/
84+
processAttributionPositioning() {
85+
if (!this.pageBackground) {
86+
this.logger.log("No background section found for attribution positioning processing");
87+
return;
88+
}
89+
this.logger.log("Processing attribution positioning for background section:", this.pageBackground);
90+
// Define all supported attribution positioning classes
91+
const allowedClasses = [
92+
"attribution-center",
93+
"attribution-bottom",
94+
"attribution-bottomcenter",
95+
"attribution-bottomright",
96+
"attribution-bottomleft",
97+
"attribution-top",
98+
"attribution-topcenter",
99+
"attribution-topright",
100+
"attribution-topleft",
101+
"attribution-left",
102+
"attribution-leftcenter",
103+
"attribution-right",
104+
"attribution-rightcenter",
105+
];
106+
try {
107+
// Find all images in the background section (after any DOM transformations)
108+
const images = this.pageBackground.querySelectorAll("img");
109+
this.logger.log("Found images in background section:", images.length);
110+
images.forEach((img) => {
111+
this.processImageAttribution(img, allowedClasses);
112+
});
113+
}
114+
catch (error) {
115+
this.logger.error("Error processing attribution positioning:", error);
116+
}
117+
}
118+
/**
119+
* Process attribution for a single image
120+
*/
121+
processImageAttribution(img, allowedClasses) {
122+
// Pattern 1: Check for class-based attribution positioning
123+
// Example: <img class="attribution-bottomright" src="...">
124+
const matchedClass = allowedClasses.find((cls) => img.classList.contains(cls));
125+
// Pattern 2: Check for data attribute-based attribution positioning
126+
// Example: <img data-background-position="bottomright" src="...">
127+
const dataPosition = img.getAttribute("data-background-position");
128+
if (matchedClass) {
129+
this.handleClassBasedAttribution(img, matchedClass);
130+
}
131+
else if (dataPosition) {
132+
this.handleDataAttributeAttribution(img, dataPosition);
133+
}
134+
}
135+
/**
136+
* Handle class-based attribution positioning
137+
*/
138+
handleClassBasedAttribution(img, matchedClass) {
139+
this.logger.log("Found attribution class on image:", matchedClass, img);
140+
const parentDiv = img.closest(".en__component--column");
141+
if (parentDiv) {
142+
// Move the class from image to parent column
143+
img.classList.remove(matchedClass);
144+
parentDiv.classList.add(matchedClass);
145+
this.logger.log("Moved attribution class from image to parent column:", matchedClass, parentDiv);
146+
}
147+
else {
148+
this.logger.log("No parent .en__component--column found for image:", img);
149+
}
150+
}
151+
/**
152+
* Handle data attribute-based attribution positioning
153+
*/
154+
handleDataAttributeAttribution(img, dataPosition) {
155+
// Convert data attribute value to attribution class format
156+
const attributionClass = `attribution-${dataPosition}`;
157+
this.logger.log("Found data-background-position on image:", dataPosition, "->", attributionClass, img);
158+
const parentDiv = img.closest(".en__component--column");
159+
if (parentDiv) {
160+
// Remove data attribute from image and add class to parent column
161+
img.removeAttribute("data-background-position");
162+
parentDiv.classList.add(attributionClass);
163+
this.logger.log("Moved data-background-position from image to parent column as class:", attributionClass, parentDiv);
164+
}
165+
else {
166+
this.logger.log("No parent .en__component--column found for image:", img);
167+
}
168+
}
169+
setupMutationObserver() {
170+
if (!this.pageBackground || !window.MutationObserver) {
171+
if (!window.MutationObserver) {
172+
this.logger.log("MutationObserver not supported in this browser");
173+
}
174+
return;
175+
}
176+
try {
177+
this.mutationObserver = new MutationObserver((mutations) => {
178+
let shouldReprocess = false;
179+
mutations.forEach((mutation) => {
180+
// Check if nodes were added or attributes changed
181+
if (mutation.type === "childList" || mutation.type === "attributes") {
182+
shouldReprocess = true;
183+
}
184+
});
185+
if (shouldReprocess) {
186+
this.logger.log("DOM changes detected in background section, reprocessing attribution classes");
187+
// Use a small delay to ensure all changes are complete
188+
setTimeout(() => {
189+
this.processAttributionPositioning();
190+
}, 100);
191+
}
192+
});
193+
// Start observing the background section
194+
this.mutationObserver.observe(this.pageBackground, {
195+
childList: true,
196+
subtree: true,
197+
attributes: true,
198+
attributeFilter: ["class"],
199+
});
200+
this.logger.log("MutationObserver set up for background section");
201+
}
202+
catch (error) {
203+
this.logger.error("Error setting up MutationObserver:", error);
204+
}
205+
}
206+
// Public method to manually trigger reprocessing
207+
reprocessAttributionPositioning() {
208+
this.logger.log("Manually reprocessing attribution positioning");
209+
this.processAttributionPositioning();
210+
}
211+
/**
212+
* Clean up resources and observers
213+
*/
214+
destroy() {
215+
if (this.mutationObserver) {
216+
this.mutationObserver.disconnect();
217+
this.mutationObserver = null;
218+
this.logger.log("MutationObserver disconnected");
35219
}
36-
this.setDataAttributes();
37220
}
38221
setDataAttributes() {
39-
if (this.hasVideoBackground())
222+
if (this.hasVideoBackground()) {
40223
return ENGrid.setBodyData("page-background", "video");
41-
if (this.hasImageBackground())
224+
}
225+
if (this.hasImageBackground()) {
42226
return ENGrid.setBodyData("page-background", "image");
227+
}
43228
return ENGrid.setBodyData("page-background", "empty");
44229
}
45230
hasVideoBackground() {
46-
if (this.pageBackground) {
47-
return !!this.pageBackground.querySelector("video");
231+
if (!this.pageBackground) {
232+
return false;
48233
}
234+
return !!this.pageBackground.querySelector("video");
49235
}
50236
hasImageBackground() {
51-
if (this.pageBackground) {
52-
return (!this.hasVideoBackground() && !!this.pageBackground.querySelector("img"));
237+
if (!this.pageBackground) {
238+
return false;
53239
}
240+
return (!this.hasVideoBackground() && !!this.pageBackground.querySelector("img"));
54241
}
55242
}

0 commit comments

Comments
 (0)