Compare commits
6 Commits
f465564546
...
d775301567
| Author | SHA1 | Date | |
|---|---|---|---|
|
d775301567
|
|||
|
ba7ca52ed2
|
|||
|
185ca863fe
|
|||
|
11456db9c4
|
|||
|
d556b0eefe
|
|||
|
007de6b9f1
|
@@ -1,7 +1,185 @@
|
||||
"use strict";
|
||||
|
||||
class IPUBElement extends HTMLElement {
|
||||
static observedAttributes = ["id"];
|
||||
|
||||
connectedCallback() {
|
||||
this.ensureID();
|
||||
}
|
||||
|
||||
attributeChangedCallback(_name, _oldValue, _newValue) {
|
||||
this.ensureID();
|
||||
}
|
||||
|
||||
ensureID() {
|
||||
if (this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// INFO: Hash algorithm by Joe Freeman & Cristian Sanchez at StackOverflow:
|
||||
// https://stackoverflow.com/a/16348977
|
||||
// https://stackoverflow.com/a/3426956
|
||||
//
|
||||
// Licensed under CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/)
|
||||
|
||||
let hash = 0;
|
||||
this.innerHTML.split("").forEach((char) => {
|
||||
hash = char.charCodeAt(0) + ((hash << 5) - hash);
|
||||
});
|
||||
|
||||
let id = "";
|
||||
for (let i = 0; i < 3; i++) {
|
||||
id += ((hash >> (i * 8)) & 0xff).toString(16).padStart(2, "0");
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
class IPUBBackground extends IPUBElement {
|
||||
static elementName = "ipub-background";
|
||||
static observedAttributes = ["nofade"].concat(super.observedAttributes);
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}),
|
||||
);
|
||||
})();
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
const image = this.querySelector("img");
|
||||
if (!image) {
|
||||
console.error("IPUBBackground: missing <img> element inside background");
|
||||
return;
|
||||
}
|
||||
|
||||
// INFO: We don't need to fade the first background
|
||||
if (this.matches(":first-of-type") || this.hasAttribute("nofade")) {
|
||||
IPUBBackground.#observer.unobserve(image);
|
||||
return;
|
||||
}
|
||||
|
||||
IPUBBackground.#observer.observe(image);
|
||||
const perc = getPercentageInView(image);
|
||||
if (perc > 0) {
|
||||
this.fade(perc);
|
||||
}
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, _oldValue, _newValue) {
|
||||
super.attributeChangedCallback();
|
||||
|
||||
if (name !== "nofade") {
|
||||
return;
|
||||
}
|
||||
|
||||
const image = this.querySelector("img");
|
||||
if (!image) {
|
||||
console.error("IPUBBackground: missing <img> element inside background");
|
||||
return;
|
||||
}
|
||||
|
||||
// INFO: We don't need to fade the first background
|
||||
if (this.matches(":first-of-type") || this.hasAttribute("nofade")) {
|
||||
IPUBBackground.#observer.unobserve(image);
|
||||
return;
|
||||
}
|
||||
|
||||
IPUBBackground.#observer.observe(image);
|
||||
const perc = getPercentageInView(image);
|
||||
if (perc > 0) {
|
||||
this.fade(perc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IPUBContent extends IPUBElement {
|
||||
static elementName = "ipub-content";
|
||||
}
|
||||
|
||||
class IPUBImage extends IPUBElement {
|
||||
static elementName = "ipub-image";
|
||||
}
|
||||
|
||||
class IPUBInteraction extends IPUBElement {
|
||||
static elementName = "ipub-interaction";
|
||||
}
|
||||
|
||||
globalThis.addEventListener("load", () => {
|
||||
console.log("IPUB SCRIPT LOADED");
|
||||
console.info("IPUB: STARTING DEFINITIONS");
|
||||
|
||||
[IPUBBackground, IPUBContent, IPUBImage, IPUBInteraction].forEach((e) => {
|
||||
console.info(`IPUB: Defining custom element <${e.elementName}>`);
|
||||
globalThis.customElements.define(e.elementName, e);
|
||||
});
|
||||
|
||||
console.info("IPUB: FINISHED DEFINITIONS");
|
||||
|
||||
/** @type {Map<string, Element>} */
|
||||
const onScreenMap = new Map();
|
||||
@@ -9,14 +187,14 @@ globalThis.addEventListener("load", () => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((e) => {
|
||||
if (e.intersectionRatio > 0) {
|
||||
console.debug(
|
||||
`IntersectionObserver: adding element #${e.target.id} to onScreenMap`,
|
||||
);
|
||||
// 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`,
|
||||
);
|
||||
// console.debug(
|
||||
// `IntersectionObserver: removing element #${e.target.id} to onScreenMap`,
|
||||
// );
|
||||
onScreenMap.delete(e.target.id);
|
||||
}
|
||||
});
|
||||
@@ -31,7 +209,7 @@ globalThis.addEventListener("load", () => {
|
||||
document.addEventListener("scroll", async () => {
|
||||
for (const [id, element] of onScreenMap) {
|
||||
const perc = getPercentageInView(element);
|
||||
console.debug(`Element #${id} is now ${perc}% on screen`);
|
||||
// console.debug(`Element #${id} is now ${perc}% on screen`);
|
||||
|
||||
const played = element.getAttribute("data-ipub-trigger-played") == "true";
|
||||
|
||||
@@ -52,7 +230,7 @@ async function playIpubElement(element) {
|
||||
/** @type {HTMLAudioElement} */
|
||||
const audio = element;
|
||||
|
||||
await audio.play();
|
||||
// await audio.play();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="x-ipub-version" content="1.0" />
|
||||
<meta name="x-ipub-version" content="0.1" />
|
||||
<link href="../styles/stylesheet.css" rel="stylesheet" type="text/css" />
|
||||
<!-- <script type="module" src="../scripts/ipub.js" fetchpriority="high"></script> -->
|
||||
<script defer="true" src="../scripts/ipub.js" fetchpriority="high">
|
||||
@@ -9,89 +9,76 @@
|
||||
</script>
|
||||
</head>
|
||||
<body xmlns:epub="http://www.idpf.org/2007/ops" class="body">
|
||||
<main data-ipub-element="content">
|
||||
<section data-ipub-element="page" id="page01">
|
||||
<span data-ipub-element="image">
|
||||
<ipub-content style="--ipub-padding: 10%;">
|
||||
<main>
|
||||
<ipub-background id="background0001">
|
||||
<img src="../images/background0001.jpg" width="100" height="100" />
|
||||
</ipub-background>
|
||||
<ipub-image>
|
||||
<img src="../images/image0001.png" />
|
||||
</span>
|
||||
<!--
|
||||
This in the UI would be an "Point Interaction" or just "Interaction". The
|
||||
editor can just place it on some point the page, and adjust it's size.
|
||||
<ipub-interaction style="--ipub-y:88.5%;--ipub-x:6%" circle="">
|
||||
<a href="https://krita.org" referrerpolicy="same-origin"
|
||||
rel="external nofollow noopener noreferrer" target="_blank" />
|
||||
</ipub-interaction>
|
||||
<ipub-interaction style="--ipub-y:93.5%;--ipub-x:81.5%;--ipub-size:13%;">
|
||||
<a href="https://guz.one" referrerpolicy="same-origin"
|
||||
rel="external nofollow noopener noreferrer" target="_blank" />
|
||||
</ipub-interaction>
|
||||
</ipub-image>
|
||||
<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 action is "open link", this action should have a warning to the reader,
|
||||
to make sure they don't open malicious links.
|
||||
-->
|
||||
<!--
|
||||
The "rel" will have "nofollow", "noopener" and "noreferrer" when the link
|
||||
is to a domain different from the project's one.
|
||||
-->
|
||||
<a data-ipub-element="interaction" data-ipub-variant="point"
|
||||
style="--ipub-x:6%;--ipub-y:88.5%;--ipub-width:10%;--ipub-radius:100%;--ipub-origin-offset-x:-50%;--ipub-origin-offset-y:-50%;--ipub-ratio:1/1;"
|
||||
id="int-httpsguzone" href="https://krita.org" target="_blank" referrerpolicy="same-origin"
|
||||
rel="external nofollow noopener noreferrer">
|
||||
<!--
|
||||
|
||||
This would be generated if the editor doesn't specify a accessibility text,
|
||||
the in quotations text would be fetched from the site's title when the link is created
|
||||
if possible.
|
||||
-->
|
||||
Go to "Krita | Digital Paiting. Creative Freedom"</a>
|
||||
<!--
|
||||
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 action is "go to page".
|
||||
-->
|
||||
<a data-ipub-element="interaction" data-ipub-variant="area"
|
||||
style="--ipub-x:76%;--ipub-y:90%;--ipub-width:11.5%;--ipub-height:8%;" id="int-httpsguzone"
|
||||
href="section0001.xhtml#page03">
|
||||
<!--
|
||||
This would be generated if the editor doesn't specify a accessibility text.
|
||||
The in quotations text would be the title of the page if it has one, otherwise
|
||||
it's ID is used (RFC, we could just place the text as "Go to page", since the IDs.
|
||||
may not be human-readable).
|
||||
-->
|
||||
Go to page "page03"</a>
|
||||
<!--
|
||||
TODO: Analyse if area and point interactions should be saved as the same type of element
|
||||
and if the "data-ipub-variant" should be a thing. This pretty much depends on how much
|
||||
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>
|
||||
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>
|
||||
<ipub-background id="background0002">
|
||||
<picture>
|
||||
<img src="../images/background0002.jpg" />
|
||||
</picture>
|
||||
</ipub-background>
|
||||
<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>
|
||||
<ipub-background id="background0003">
|
||||
<picture>
|
||||
<img src="../images/background0003.jpg" />
|
||||
</picture>
|
||||
</ipub-background>
|
||||
<section data-ipub-element="page" id="page02">
|
||||
<span data-ipub-element="image">
|
||||
<img src="../images/image0002.png" />
|
||||
</span>
|
||||
</section>
|
||||
<section data-ipub-element="page" id="page04">
|
||||
<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>
|
||||
</main>
|
||||
</ipub-content>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,22 +1,164 @@
|
||||
.body {
|
||||
-epub-writing-mode: horizontal-tb;
|
||||
-webkit-writing-mode: horizontal-tb;
|
||||
/* direction: ltr; */
|
||||
direction: rtl;
|
||||
direction: ltr;
|
||||
/* direction: rtl; */
|
||||
writing-mode: horizontal-tb;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
[data-ipub-element="page"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
ipub-content {
|
||||
--ipub-padding: 0%;
|
||||
--ipub-gap: 0%;
|
||||
--ipub-padding-x: var(--ipub-padding, 0%);
|
||||
--ipub-padding-y: var(--ipub-padding, 0%);
|
||||
--ipub-padding-t: var(--ipub-padding-y, 0%);
|
||||
--ipub-padding-r: var(--ipub-padding-x, 0%);
|
||||
--ipub-padding-b: var(--ipub-padding-y, 0%);
|
||||
--ipub-padding-l: var(--ipub-padding-x, 0%);
|
||||
|
||||
& > article,
|
||||
& > main,
|
||||
& > section {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
& > *:first-child:not(ipub-background),
|
||||
& > ipub-background:first-child + *:first-of-type {
|
||||
margin-top: var(--ipub-padding-t);
|
||||
}
|
||||
|
||||
& > *:not(ipub-background) {
|
||||
margin-top: calc(var(--ipub-gap) / 2);
|
||||
margin-right: var(--ipub-padding-r);
|
||||
margin-left: var(--ipub-padding-l);
|
||||
margin-bottom: calc(var(--ipub-gap) / 2);
|
||||
}
|
||||
|
||||
& > *:last-child:not(ipub-background),
|
||||
& > ipub-background:last-child + *:last-of-type {
|
||||
margin-bottom: var(--ipub-padding-b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipub-background {
|
||||
--ipub-width: 100vw;
|
||||
--ipub-height: 100vh;
|
||||
|
||||
&[sticky] {
|
||||
display: inline-block;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: sticky;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
&[fade] 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);
|
||||
}
|
||||
|
||||
& > picture {
|
||||
display: block;
|
||||
width: var(--ipub-width);
|
||||
height: var(--ipub-height);
|
||||
& > img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
/* Support standalone img element */
|
||||
& > img {
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
width: var(--ipub-width);
|
||||
height: var(--ipub-height);
|
||||
}
|
||||
}
|
||||
|
||||
ipub-image {
|
||||
position: relative;
|
||||
display: block;
|
||||
flex-direction: column;
|
||||
|
||||
width: var(--ipub-width, unset);
|
||||
height: var(--ipub-height, unset);
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
ipub-interaction {
|
||||
position: absolute;
|
||||
|
||||
--ipub-x: 0px;
|
||||
--ipub-y: 0px;
|
||||
--ipub-size: 10%;
|
||||
--ipub-width: var(--ipub-size, unset);
|
||||
--ipub-height: unset;
|
||||
--ipub-ratio: 1/1;
|
||||
|
||||
left: var(--ipub-x);
|
||||
top: var(--ipub-y);
|
||||
|
||||
width: var(--ipub-width);
|
||||
height: var(--ipub-height);
|
||||
aspect-ratio: var(--ipub-ratio, unset);
|
||||
transform: translate(var(--ipub-offset-x, -50%), var(--ipub-offset-y, -50%));
|
||||
|
||||
& > * {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border-radius: var(--ipub-radius, unset);
|
||||
|
||||
/*
|
||||
* The opacity would be, by default, zero. Here it is 0.3 for easier debugging and
|
||||
* showing of the example ebook
|
||||
*/
|
||||
background-color: red;
|
||||
opacity: 0.3;
|
||||
}
|
||||
&[circle] {
|
||||
--ipub-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-ipub-element="image"] {
|
||||
width: var(--ipub-width, unset);
|
||||
height: var(--ipub-width, unset);
|
||||
height: var(--ipub-height, unset);
|
||||
background-image: var(--ipub-image, unset);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
display: block;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
[data-ipub-element="interaction"] {
|
||||
@@ -44,7 +186,4 @@ a[data-ipub-element="interaction"] {
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user