Files
comicverse/.epub/example/OEBPS/scripts/ipub.js

223 lines
5.4 KiB
JavaScript
Raw Normal View History

"use strict";
/**
* @param {string} str
* @returns {string}
*/
function hashString(str) {
return Array.from(str).reduce(
(s, c) => (Math.imul(31, s) + c.charCodeAt(0)) | 0,
0,
);
}
class IPUBBackground extends HTMLElement {
static elementName = "ipub-background";
static observedAttributes = ["sticky", "fade", "id"];
/**
* @private
*/
static #observer = (() => {
/** @type {Map<string, IPUBBackground>} */
const instancesOnScreen = new Map();
document.addEventListener("scroll", () => {
for (const [_, instance] of instancesOnScreen) {
const perc = getPercentageInView(
instance.querySelector("img") || instance,
);
instance.fade(perc);
}
});
return new IntersectionObserver((entries) =>
entries.forEach((e) => {
let instance = e.target.parentElement;
if (
instance.tagName.toLowerCase() !==
IPUBBackground.elementName.toLowerCase()
) {
instance = instance.parentElement;
}
if (
instance.tagName.toLowerCase() !==
IPUBBackground.elementName.toLowerCase()
) {
console.error(
"IPUBBackground: malformed <ipub-background> element",
e.target,
);
return;
}
if (e.intersectionRatio > 0 && instance.id) {
instancesOnScreen.set(instance.id, instance);
} else if (instance.id) {
instancesOnScreen.delete(instance.id);
}
}),
);
})();
attributeChangedCallback(name, _oldValue, _newValue) {
console.debug("IPUBBackground: attribute changed", name);
switch (name) {
case "id": {
if (!this.id) {
console.warn(
`IPUBBackground: no ID specified, assigning one based on innerHTML`,
this,
);
this.id = hashString(this.innerHTML);
}
break;
}
case "fade": {
const image = this.querySelector("img");
if (image) {
console.debug(
`IPUBBackground: ipub-background#${this.id} to observer`,
);
if (this.hasAttribute("fade")) {
IPUBBackground.#observer.observe(image);
} else {
IPUBBackground.#observer.unobserve(image);
}
const perc = getPercentageInView(image);
if (perc > 0) {
this.fade(perc);
}
}
break;
}
}
}
/**
* @param {number} perc
* @throws {Error}
* @returns {void | Promise<void>}
*/
fade(perc) {
console.debug(`IPUBBackground: ${this.id} is ${perc} on screen`);
if (!this.style.getPropertyValue("--ipub-fade")) {
this.style.setProperty("--ipub-fade", `${perc}%`);
return;
}
const currentPerc = this.style.getPropertyValue("--ipub-fade");
if (currentPerc === "100%" && perc >= 100) {
return;
}
if (perc % 10 === 0) {
this.style.setProperty("--ipub-fade", `${perc}%`);
}
}
}
globalThis.addEventListener("load", () => {
console.log("IPUB SCRIPT LOADED");
customElements.define(IPUBBackground.elementName, IPUBBackground);
/** @type {Map<string, Element>} */
const onScreenMap = new Map();
const observer = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.intersectionRatio > 0) {
// console.debug(
// `IntersectionObserver: adding element #${e.target.id} to onScreenMap`,
// );
onScreenMap.set(e.target.id, e.target);
} else {
// console.debug(
// `IntersectionObserver: removing element #${e.target.id} to onScreenMap`,
// );
onScreenMap.delete(e.target.id);
}
});
});
for (const element of document.querySelectorAll(
`[data-ipub-trigger="on-screen"]`,
)) {
observer.observe(element);
}
document.addEventListener("scroll", async () => {
for (const [id, element] of onScreenMap) {
const perc = getPercentageInView(element);
// console.debug(`Element #${id} is now ${perc}% on screen`);
const played = element.getAttribute("data-ipub-trigger-played") == "true";
if (perc >= 100 && !played) {
await playIpubElement(element);
element.setAttribute("data-ipub-trigger-played", "true");
}
}
});
});
/**
* @param {Element} element
*/
async function playIpubElement(element) {
switch (element.tagName) {
case "audio": {
/** @type {HTMLAudioElement} */
const audio = element;
// await audio.play();
break;
}
default:
break;
}
}
/**
* @param {Element} element
* @returns {number}
*/
function getPercentageInView(element) {
const viewTop = globalThis.pageYOffset;
const viewBottom = viewTop + globalThis.innerHeight;
const rect = element.getBoundingClientRect();
const elementTop = rect.y + viewTop;
const elementBottom = rect.y + rect.height + viewTop;
if (viewTop > elementBottom || viewBottom < elementTop) {
return 0;
}
if (
(viewTop < elementTop && viewBottom > elementBottom) ||
(elementTop < viewTop && elementBottom > viewBottom)
) {
return 100;
}
let inView = rect.height;
if (elementTop < viewTop) {
inView = rect.height - (globalThis.pageYOffset - elementTop);
}
if (elementBottom > viewBottom) {
inView = inView - (elementBottom - viewBottom);
}
return Math.round((inView / globalThis.innerHeight) * 100);
}