feat(ipub): on-screen interaction trigger

This commit is contained in:
Guz
2025-08-12 17:17:30 -03:00
parent fc6afa28d7
commit eeb4d2b9b3
2 changed files with 131 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
"use strict";
globalThis.addEventListener("load", () => {
console.log("IPUB SCRIPT LOADED");
/** @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);
}

View File

@@ -59,6 +59,39 @@
we want the editor to "guess" what controls to provide the user with.
-->
</section>
<section data-ipub-element="page" id="page02">
<span data-ipub-element="image">
<img src="../images/image0002.png" />
</span>
<!--
This in the UI would be an "Area Interaction". The editor would first place
the first top-left point, and then the bottom-right one, to select an area/size
of the interaction.
The element wound not have a "action" per say, but would have a "on screen" trigger,
which in itself would have the action "play sound".
-->
<audio data-ipub-element="interaction" data-ipub-trigger="on-screen" controls="true"
volume="0" style="--ipub-x:20%;--ipub-y:25%;--ipub-width:50%;--ipub-height:50%;"
id="int-audio0001">
<source src="../audios/audio0001.wav.disable" />
</audio>
</section>
<section data-ipub-element="page" id="page03">
<span data-ipub-element="image">
<img src="../images/image0003.png" />
</span>
</section>
<section data-ipub-element="page" id="page04">
<span data-ipub-element="image">
<img src="../images/image0004.png" />
</span>
</section>
<section data-ipub-element="page" id="page02">
<span data-ipub-element="image">
<img src="../images/image0002.png" />
</span>
</section>
</main>
</body>
</html>