From a3c2efd5b0f95a3896199e61f0414a1b2d6704a2 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L de Mello" Date: Thu, 23 Oct 2025 15:00:32 -0300 Subject: [PATCH] feat(ipub): make ipub-background containerized into ipub-body --- .epub/example/OEBPS/scripts/ipub.js | 124 +++++++++++++++------- .epub/example/OEBPS/styles/stylesheet.css | 40 ++++--- 2 files changed, 110 insertions(+), 54 deletions(-) diff --git a/.epub/example/OEBPS/scripts/ipub.js b/.epub/example/OEBPS/scripts/ipub.js index 1a9e3e2..f70a681 100644 --- a/.epub/example/OEBPS/scripts/ipub.js +++ b/.epub/example/OEBPS/scripts/ipub.js @@ -28,49 +28,84 @@ class IPUBBackground extends IPUBElement { /** * @private */ - static #observer = (() => { - /** @type {Map} */ - const instancesOnScreen = new Map(); - - document.addEventListener("scroll", () => { - for (const [_, instance] of instancesOnScreen) { - const perc = getPercentageInView( - instance.querySelector("img") || instance, + static #instancesOnScreen = { + /** @type {Map>} */ + map: new Map(), + /** + * @param {IPUBBackground} background + */ + add(background) { + const body = getAncestor(background, IPUBBody.elementName); + if (!body) { + console.error( + `IPUBBackground: ${background.id} does not have a valid ipub-body ancestor`, ); - instance.fade(perc); + return; } - }); - return new IntersectionObserver((entries) => - entries.forEach((e) => { - let instance = e.target.parentElement; + let set = this.map.get(body); + if (!set) { + set = new Set(); + body.addEventListener("scroll", () => { + for (const instance of set.values()) { + const perc = getPercentageInView( + instance.querySelector("img") || instance, + ); + instance.fade(perc); + } + }); + } + set.add(background); + this.map.set(body, set); + }, + /** + * @param {IPUBBackground} background + */ + remove(background) { + const body = getAncestor(background, IPUBBody.elementName); + if (!body) { + console.error( + `IPUBBackground: ${background.id} does not have a valid ipub-body ancestor`, + ); + return; + } - if ( - instance.tagName.toLowerCase() !== - IPUBBackground.elementName.toLowerCase() - ) { - instance = instance.parentElement; - } + const set = this.map.get(body); + if (!set) { + return; + } - if ( - instance.tagName.toLowerCase() !== - IPUBBackground.elementName.toLowerCase() - ) { - console.error( - "IPUBBackground: malformed element", - e.target, - ); - return; - } + set.delete(background); - if (e.intersectionRatio > 0 && instance.id) { - instancesOnScreen.set(instance.id, instance); - } else if (instance.id) { - instancesOnScreen.delete(instance.id); - } - }), - ); - })(); + if (set.size === 0) { + this.map.delete(body); + } + }, + }; + static addToScreen() {} + + /** + * @private + */ + static #observer = new IntersectionObserver((entries) => { + for (const { intersectionRatio, target: image } of entries) { + const instance = getAncestor(image, IPUBBackground.elementName); + + if (!instance) { + console.error( + "IPUBBackground: malformed element", + image, + ); + return; + } + + if (intersectionRatio > 0 && instance.id) { + IPUBBackground.#instancesOnScreen.add(instance); + } else if (instance.id) { + IPUBBackground.#instancesOnScreen.remove(instance); + } + } + }); connectedCallback() { super.connectedCallback(); @@ -126,8 +161,6 @@ class IPUBBackground extends IPUBElement { * @returns {void | Promise} */ fade(perc) { - console.debug(`IPUBBackground: ${this.id} is ${perc} on screen`); - if (!this.style.getPropertyValue("--ipub-fade")) { this.style.setProperty("--ipub-fade", `${perc}%`); return; @@ -264,7 +297,19 @@ class IPUBInteraction extends IPUBElement { } /** + * @param {HTMLElement} el + * @param {string} tagName + * @returns {HTMLElement | undefined} */ +function getAncestor(el, tagName) { + if (!el.parentElement) { + return undefined; + } + if (el.parentElement.tagName.toLowerCase() === tagName.toLowerCase()) { + return el.parentElement; + } + return getAncestor(el.parentElement, tagName); +} /** * @param {Readonly} el @@ -330,3 +375,4 @@ function getPercentageInView(element) { return Math.round((inView / globalThis.innerHeight) * 100); } + diff --git a/.epub/example/OEBPS/styles/stylesheet.css b/.epub/example/OEBPS/styles/stylesheet.css index c24ae32..77388df 100644 --- a/.epub/example/OEBPS/styles/stylesheet.css +++ b/.epub/example/OEBPS/styles/stylesheet.css @@ -84,36 +84,43 @@ ipub-body { } ipub-background { + --ipub-mask: linear-gradient( + rgba(0, 0, 0, 0) 0%, + rgba(0, 0, 0, 1) calc(100% + calc(var(--ipub-fade, 100%) * -1)) + ); + --ipub-width: 100vw; --ipub-height: 100vh; - &[sticky] { - display: inline-block; - top: 0; - left: 0; - width: 0; - height: 0; - position: sticky; - align-self: start; + display: inline-block; + top: 0; + left: 0; + width: 0; + height: 0; + position: sticky; + align-self: start; + + &:first-of-type, + &[nofade] { + --ipub-mask: unset; } - &[fade] img { + img { /* For testing */ /* background-image: linear-gradient( */ /* rgba(266, 0, 0, 1) 0%, */ /* rgba(0, 266, 0, 1) calc(100% + calc(var(--ipub-fade, 100%) * -1)), */ /* rgba(266, 0, 266, 1) 100% */ /* ) !important; */ - --mask: linear-gradient( - rgba(0, 0, 0, 0) 0%, - rgba(0, 0, 0, 1) calc(100% + calc(var(--ipub-fade, 100%) * -1)) - ) !important; /* background-image: var(--mask); */ - mask-image: var(--mask); - -webkit-mask-image: var(--mask); + mask-image: var(--ipub-mask); + -webkit-mask-image: var(--ipub-mask); } & > picture { + position: absolute; + top: 0; + left: 0; display: block; width: var(--ipub-width); height: var(--ipub-height); @@ -125,6 +132,9 @@ ipub-background { } /* Support standalone img element */ & > img { + position: absolute; + top: 0; + left: 0; display: block; object-fit: cover; width: var(--ipub-width);