Compare commits
5 Commits
6f0b6892a8
...
test/ipub-
| Author | SHA1 | Date | |
|---|---|---|---|
|
e1734a5310
|
|||
|
f76be67247
|
|||
|
c90bff53a3
|
|||
|
7d1a21430c
|
|||
|
a3c2efd5b0
|
@@ -1,14 +1,29 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
class IPUBElement extends HTMLElement {
|
class IPUBElement extends HTMLElement {
|
||||||
static observedAttributes = ["id"];
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {Readonly<string[]>}
|
||||||
|
*/
|
||||||
|
static observedAttributes = ["id", "debug"];
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.#ensureID();
|
this.#ensureID();
|
||||||
}
|
}
|
||||||
|
|
||||||
attributeChangedCallback(_name, _oldValue, _newValue) {
|
attributeChangedCallback(name, _oldValue, _newValue) {
|
||||||
this.#ensureID();
|
switch (name) {
|
||||||
|
case "id":
|
||||||
|
this.#ensureID();
|
||||||
|
break;
|
||||||
|
case "debug":
|
||||||
|
if (this.hasAttribute("debug")) {
|
||||||
|
this.setDebug(true);
|
||||||
|
} else {
|
||||||
|
this.setDebug(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +34,339 @@ class IPUBElement extends HTMLElement {
|
|||||||
this.id = hashFromHTML(this);
|
this.id = hashFromHTML(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getDebug() {
|
||||||
|
return this.hasAttribute("debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} state
|
||||||
|
*/
|
||||||
|
setDebug(state) {
|
||||||
|
if (state) {
|
||||||
|
if (!this.hasAttribute("debug")) {
|
||||||
|
this.setAttribute("debug", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.style.getPropertyValue(IPUBElement.#PROPERTY_DEBUG_COLOR)) {
|
||||||
|
this.style.setProperty(
|
||||||
|
IPUBElement.#PROPERTY_DEBUG_COLOR,
|
||||||
|
`#${hashFromHTML(this)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!state && this.hasAttribute("debug")) {
|
||||||
|
this.removeAttribute("debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!state &&
|
||||||
|
this.style.getPropertyValue(IPUBElement.#PROPERTY_DEBUG_COLOR)
|
||||||
|
) {
|
||||||
|
this.style.removeProperty(IPUBElement.#PROPERTY_DEBUG_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllDescendants(this)
|
||||||
|
.filter((el) => el.tagName.startsWith("ipub-"))
|
||||||
|
.forEach((el) => {
|
||||||
|
el.setDebug?.(state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Readonly<string>}
|
||||||
|
*/
|
||||||
|
static #PROPERTY_DEBUG_COLOR = "--ipub-debug-color";
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.addEventListener("load", () => {
|
||||||
|
console.info("IPUB: Starting IPUB elements");
|
||||||
|
|
||||||
|
console.log("IPUB: Defining custom element <ipub-body>");
|
||||||
|
globalThis.customElements.define(IPUBBody.elementName, IPUBBody);
|
||||||
|
});
|
||||||
|
|
||||||
|
class IPUBBody extends IPUBElement {
|
||||||
|
static elementName = `ipub-body`;
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
this.setAttribute("aria-busy", "true");
|
||||||
|
|
||||||
|
// TODO?: Move IPUBCover's "can-play" logic to here
|
||||||
|
|
||||||
|
console.log("IPUBBody: Defining custom element <ipub-cover>");
|
||||||
|
globalThis.customElements.define(IPUBCover.elementName, IPUBCover);
|
||||||
|
|
||||||
|
/** @type {IPUBCover} */
|
||||||
|
const cover = this.querySelector("ipub-cover");
|
||||||
|
if (!cover) {
|
||||||
|
// TODO: automatically create IPUBCover element if it doesn't exists
|
||||||
|
console.error("IPUBBody: Document doesn't has <ipub-cover> element");
|
||||||
|
this.#initElements();
|
||||||
|
this.setAttribute("aria-busy", "false");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cover.onclose = () => {
|
||||||
|
this.#initElements();
|
||||||
|
};
|
||||||
|
cover.cover();
|
||||||
|
|
||||||
|
this.setAttribute("aria-busy", "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
#initElements() {
|
||||||
|
for (const e of [
|
||||||
|
IPUBAudio,
|
||||||
|
IPUBBackground,
|
||||||
|
IPUBImage,
|
||||||
|
IPUBInteraction,
|
||||||
|
IPUBSoundtrack,
|
||||||
|
IPUBTrack,
|
||||||
|
IPUBTrackItem,
|
||||||
|
IPUBTrackItemPosition,
|
||||||
|
]) {
|
||||||
|
console.info(`IPUBBody: Defining custom element <${e.elementName}>`);
|
||||||
|
globalThis.customElements.define(e.elementName, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getDebug()) {
|
||||||
|
// HACK: Re-trigger IPUBElement debugging logic
|
||||||
|
console.debug("IPUBBody: triggeing debugger");
|
||||||
|
this.setAttribute("debug", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPUBCover extends IPUBElement {
|
||||||
|
static elementName = `ipub-cover`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {() => void} callback
|
||||||
|
*/
|
||||||
|
onclose = () => {};
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
cover() {
|
||||||
|
console.debug("IPUBCover: Setting up cover");
|
||||||
|
this.setAttribute("aria-busy", "true");
|
||||||
|
|
||||||
|
const dialog = this.querySelector("dialog");
|
||||||
|
|
||||||
|
// HACK: Test if we can autoplay interactions, soundtracks, etc
|
||||||
|
|
||||||
|
/** @type {HTMLMediaElement | null} */
|
||||||
|
const media =
|
||||||
|
this.parentElement.querySelector("audio") ??
|
||||||
|
this.parentElement.querySelector("video");
|
||||||
|
|
||||||
|
if (!media) {
|
||||||
|
console.log("IPUBCover: no media element found, removing cover");
|
||||||
|
dialog.close();
|
||||||
|
this.onclose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pastVolume = media.volume;
|
||||||
|
media.volume = 0.1; // don't let the user hear the test audio
|
||||||
|
|
||||||
|
media
|
||||||
|
.play()
|
||||||
|
.then(() => {
|
||||||
|
media.pause();
|
||||||
|
media.volume = pastVolume;
|
||||||
|
media.currentTime = 0;
|
||||||
|
|
||||||
|
console.debug("IPUBCover: Can autoplay interactions, removing cover");
|
||||||
|
dialog.close();
|
||||||
|
this.onclose();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.debug(
|
||||||
|
"IPUBCover: Cannot autoplay interactions, covering content",
|
||||||
|
);
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
dialog.parentElement.addEventListener("click", () => {
|
||||||
|
dialog.close();
|
||||||
|
this.onclose();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setAttribute("aria-busy", "false");
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPUBTrack extends IPUBElement {
|
||||||
|
static elementName = "ipub-track";
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPUBTrackItem extends IPUBElement {
|
||||||
|
static elementName = `ipub-track-item`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPUBTrackItemPosition extends IPUBElement {
|
||||||
|
static elementName = `ipub-track-item-position`;
|
||||||
|
}
|
||||||
|
|
||||||
|
class IPUBAudio extends IPUBElement {
|
||||||
|
static elementName = "ipub-audio";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} [forced=false]
|
||||||
|
* @throws {Error}
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
play(forced = false) {
|
||||||
|
if (!this.#audioElement.readyState > HTMLMediaElement.HAVE_CURRENT_DATA) {
|
||||||
|
throw new Error("IPUBAudio: audio is not ready");
|
||||||
|
}
|
||||||
|
if (forced) {
|
||||||
|
this.setAttribute("forced", "true");
|
||||||
|
}
|
||||||
|
return this.#audioElement.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} [forced=false]
|
||||||
|
*/
|
||||||
|
pause(forced = false) {
|
||||||
|
if (forced) {
|
||||||
|
this.setAttribute("forced", "true");
|
||||||
|
}
|
||||||
|
this.#audioElement.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} state
|
||||||
|
*/
|
||||||
|
setLoop(state) {
|
||||||
|
this.#audioElement.loop = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
getLoop() {
|
||||||
|
return this.#audioElement.loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
const audio = this.querySelector("audio");
|
||||||
|
if (!audio) {
|
||||||
|
console.error("IPUBAudio: Missing child <audio> element");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#audioElement = audio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {HTMLAudioElement}
|
||||||
|
*/
|
||||||
|
#audioElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} volume
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {number} [options.fadetime=0]
|
||||||
|
* @param {() => void} [options.onfinish=() => {}]
|
||||||
|
*/
|
||||||
|
setVolume(volume, { fadetimeMS = 0, onFinishFade = () => {} } = {}) {
|
||||||
|
if (fadetimeMS === 0) {
|
||||||
|
this.#audioElement.volume = volume / 100;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#isFading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#onFinishFade = onFinishFade;
|
||||||
|
|
||||||
|
const diff = volume - this.#audioElement.volume * 100;
|
||||||
|
const ticks = diff < 0 ? Math.abs(diff) : diff;
|
||||||
|
let tick = 0;
|
||||||
|
|
||||||
|
const interval = fadetimeMS / ticks;
|
||||||
|
|
||||||
|
this.#isFading = true;
|
||||||
|
this.#fadeTask = setInterval(() => {
|
||||||
|
tick++;
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
this.#isFading = false;
|
||||||
|
if (onFinishFade) {
|
||||||
|
onFinishFade();
|
||||||
|
}
|
||||||
|
clearInterval(this.#fadeTask);
|
||||||
|
this.#onFinishFade = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.#audioElement) {
|
||||||
|
cancel();
|
||||||
|
console.error("IPUBAudio: Missing child <audio> element");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ticks < tick) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volume === this.getVolume()) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff === 0) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nvol =
|
||||||
|
(diff > 0
|
||||||
|
? Math.ceil(this.#audioElement.volume * 100 + 1)
|
||||||
|
: Math.floor(this.#audioElement.volume * 100 - 1)) / 100;
|
||||||
|
|
||||||
|
if (nvol > 1 || nvol < 0) {
|
||||||
|
cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#audioElement.volume = nvol;
|
||||||
|
}, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getVolume() {
|
||||||
|
return Math.floor((this.#audioElement?.volume ?? 0) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#isFading = false;
|
||||||
|
#fadeTask = 0;
|
||||||
|
/** @type {() => void | null} */
|
||||||
|
#onFinishFade = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class IPUBBackground extends IPUBElement {
|
class IPUBBackground extends IPUBElement {
|
||||||
@@ -28,49 +376,84 @@ class IPUBBackground extends IPUBElement {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
static #observer = (() => {
|
static #instancesOnScreen = {
|
||||||
/** @type {Map<string, IPUBBackground>} */
|
/** @type {Map<IPUBBody, Set<IPUBBackground>>} */
|
||||||
const instancesOnScreen = new Map();
|
map: new Map(),
|
||||||
|
/**
|
||||||
document.addEventListener("scroll", () => {
|
* @param {IPUBBackground} background
|
||||||
for (const [_, instance] of instancesOnScreen) {
|
*/
|
||||||
const perc = getPercentageInView(
|
add(background) {
|
||||||
instance.querySelector("img") || instance,
|
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) =>
|
let set = this.map.get(body);
|
||||||
entries.forEach((e) => {
|
if (!set) {
|
||||||
let instance = e.target.parentElement;
|
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 (
|
const set = this.map.get(body);
|
||||||
instance.tagName.toLowerCase() !==
|
if (!set) {
|
||||||
IPUBBackground.elementName.toLowerCase()
|
return;
|
||||||
) {
|
}
|
||||||
instance = instance.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
set.delete(background);
|
||||||
instance.tagName.toLowerCase() !==
|
|
||||||
IPUBBackground.elementName.toLowerCase()
|
|
||||||
) {
|
|
||||||
console.error(
|
|
||||||
"IPUBBackground: malformed <ipub-background> element",
|
|
||||||
e.target,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.intersectionRatio > 0 && instance.id) {
|
if (set.size === 0) {
|
||||||
instancesOnScreen.set(instance.id, instance);
|
this.map.delete(body);
|
||||||
} else if (instance.id) {
|
}
|
||||||
instancesOnScreen.delete(instance.id);
|
},
|
||||||
}
|
};
|
||||||
}),
|
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 <ipub-background> element",
|
||||||
|
image,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intersectionRatio > 0 && instance.id) {
|
||||||
|
IPUBBackground.#instancesOnScreen.add(instance);
|
||||||
|
} else if (instance.id) {
|
||||||
|
IPUBBackground.#instancesOnScreen.remove(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@@ -126,8 +509,6 @@ class IPUBBackground extends IPUBElement {
|
|||||||
* @returns {void | Promise<void>}
|
* @returns {void | Promise<void>}
|
||||||
*/
|
*/
|
||||||
fade(perc) {
|
fade(perc) {
|
||||||
console.debug(`IPUBBackground: ${this.id} is ${perc} on screen`);
|
|
||||||
|
|
||||||
if (!this.style.getPropertyValue("--ipub-fade")) {
|
if (!this.style.getPropertyValue("--ipub-fade")) {
|
||||||
this.style.setProperty("--ipub-fade", `${perc}%`);
|
this.style.setProperty("--ipub-fade", `${perc}%`);
|
||||||
return;
|
return;
|
||||||
@@ -143,110 +524,6 @@ class IPUBBackground extends IPUBElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IPUBBody extends IPUBElement {
|
|
||||||
static elementName = "ipub-body";
|
|
||||||
|
|
||||||
static defineContentElements() {
|
|
||||||
for (const e of [
|
|
||||||
IPUBBackground,
|
|
||||||
IPUBImage,
|
|
||||||
IPUBInteraction,
|
|
||||||
]) {
|
|
||||||
console.info(`IPUBBody: Defining custom element <${e.elementName}>`);
|
|
||||||
globalThis.customElements.define(e.elementName, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
|
|
||||||
this.setAttribute("aria-busy", "true");
|
|
||||||
|
|
||||||
// TODO?: Move IPUBCover's "can-play" logic to here
|
|
||||||
|
|
||||||
console.log("IPUBBody: Defining custom element <ipub-cover>");
|
|
||||||
globalThis.customElements.define(IPUBCover.elementName, IPUBCover);
|
|
||||||
|
|
||||||
/** @type {IPUBCover} */
|
|
||||||
const cover = this.querySelector("ipub-cover");
|
|
||||||
if (!cover) {
|
|
||||||
// TODO: automatically create IPUBCover element if it doesn't exists
|
|
||||||
console.error("IPUBBody: Document doesn't has <ipub-cover> element");
|
|
||||||
IPUBBody.defineContentElements();
|
|
||||||
}
|
|
||||||
cover.onclose = IPUBBody.defineContentElements;
|
|
||||||
|
|
||||||
this.setAttribute("aria-busy", "false");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalThis.addEventListener("load", () => {
|
|
||||||
console.info("IPUB: Starting IPUB elements");
|
|
||||||
|
|
||||||
console.log("IPUB: Defining custom element <ipub-body>");
|
|
||||||
globalThis.customElements.define(IPUBBody.elementName, IPUBBody);
|
|
||||||
});
|
|
||||||
|
|
||||||
class IPUBCover extends IPUBElement {
|
|
||||||
static elementName = "ipub-cover";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {() => void} callback
|
|
||||||
*/
|
|
||||||
onclose = () => {};
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
|
|
||||||
console.debug("IPUBCover: Setting up cover");
|
|
||||||
this.setAttribute("aria-busy", "true");
|
|
||||||
|
|
||||||
const dialog = this.querySelector("dialog");
|
|
||||||
|
|
||||||
// HACK: Test if we can autoplay interactions, soundtracks, etc
|
|
||||||
|
|
||||||
/** @type {HTMLMediaElement | null} */
|
|
||||||
const media =
|
|
||||||
this.parentElement.querySelector("audio") ??
|
|
||||||
this.parentElement.querySelector("video");
|
|
||||||
|
|
||||||
if (!media) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pastVolume = media.volume;
|
|
||||||
media.volume = 0.1; // don't let the user hear the test audio
|
|
||||||
|
|
||||||
media
|
|
||||||
.play()
|
|
||||||
.then(() => {
|
|
||||||
media.pause();
|
|
||||||
media.volume = pastVolume;
|
|
||||||
media.currentTime = 0;
|
|
||||||
|
|
||||||
console.debug("IPUBCover: Can autoplay interactions, removing cover");
|
|
||||||
dialog.close();
|
|
||||||
this.onclose();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.debug(
|
|
||||||
"IPUBCover: Cannot autoplay interactions, covering content",
|
|
||||||
);
|
|
||||||
|
|
||||||
dialog.show();
|
|
||||||
|
|
||||||
dialog.parentElement.addEventListener("click", () => {
|
|
||||||
dialog.close();
|
|
||||||
this.onclose();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setAttribute("aria-busy", "false");
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IPUBImage extends IPUBElement {
|
class IPUBImage extends IPUBElement {
|
||||||
static elementName = "ipub-image";
|
static elementName = "ipub-image";
|
||||||
}
|
}
|
||||||
@@ -255,16 +532,160 @@ class IPUBInteraction extends IPUBElement {
|
|||||||
static elementName = "ipub-interaction";
|
static elementName = "ipub-interaction";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
class IPUBSoundtrack extends IPUBElement {
|
||||||
});
|
static elementName = "ipub-soundtrack";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Toggle automatic soundtrack playing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static #player = setInterval(() => {
|
||||||
|
const last = Array.from(this.#onScreenStack).pop();
|
||||||
|
if (!last) {
|
||||||
|
// TODO: Fallback to previous soundtrack if there's no audio
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Get siblings based by group OR parent
|
||||||
|
/** @type {NodeListOf<IPUBSoundtrack> | undefined} */
|
||||||
|
const siblings = last.parentElement?.querySelectorAll(
|
||||||
|
IPUBSoundtrack.elementName,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (siblings) {
|
||||||
|
siblings.forEach((el) => {
|
||||||
|
if (el !== last) {
|
||||||
|
el.fadeOut();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
last.fadeIn();
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Fallback to previous soundtrack on error
|
||||||
|
console.error(
|
||||||
|
`IPUBSoundtrack: error while trying to play audio, error: ${e}`,
|
||||||
|
{
|
||||||
|
error: e,
|
||||||
|
audio: audio,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
static #observer = (() => {
|
||||||
|
return new IntersectionObserver((entries) => {
|
||||||
|
for (const { intersectionRatio, target, time } of entries) {
|
||||||
|
/** @type {IPUBSoundtrack} */
|
||||||
|
const soundtrack = target;
|
||||||
|
|
||||||
|
if (intersectionRatio > 0) {
|
||||||
|
console.debug(`${soundtrack.id} is on screen at ${time}`, soundtrack);
|
||||||
|
this.#onScreenStack.add(soundtrack);
|
||||||
|
} else {
|
||||||
|
console.debug(
|
||||||
|
`${soundtrack.id} is not on screen ${time}`,
|
||||||
|
soundtrack,
|
||||||
|
);
|
||||||
|
this.#onScreenStack.delete(soundtrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {Set<IPUBSoundtrack>}
|
||||||
|
*/
|
||||||
|
static #onScreenStack = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws {Error}
|
||||||
|
*/
|
||||||
|
fadeIn() {
|
||||||
|
/** @type {IPUBAudio | undefined} */
|
||||||
|
const audio = this.querySelector(IPUBAudio.elementName);
|
||||||
|
if (!audio) {
|
||||||
|
throw new Error("IPUBSoundtrack.fadeIn: missing audio element");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Global volume settings
|
||||||
|
|
||||||
|
audio.play();
|
||||||
|
audio.setVolume(10, { fadetimeMS: IPUBSoundtrack.FADE_TIME_MS });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws {Error}
|
||||||
|
*/
|
||||||
|
fadeOut() {
|
||||||
|
/** @type {IPUBAudio | undefined} */
|
||||||
|
const audio = this.querySelector(IPUBAudio.elementName);
|
||||||
|
if (!audio) {
|
||||||
|
throw new Error("IPUBSoundtrack.fadeIn: missing audio element");
|
||||||
|
}
|
||||||
|
|
||||||
|
audio.setVolume(0, {
|
||||||
|
fadetimeMS: IPUBSoundtrack.FADE_TIME_MS,
|
||||||
|
onFinishFade: () => {
|
||||||
|
audio.pause();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Readonly<number>} */
|
||||||
|
static FADE_TIME_MS = 1000 * 3;
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
/** @type {IPUBAudio | undefined} */
|
||||||
|
const audio = this.querySelector(IPUBAudio.elementName);
|
||||||
|
if (audio) {
|
||||||
|
audio.setVolume(0);
|
||||||
|
} else {
|
||||||
|
console.error("IPUBSoundtrack: missing audio element");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPUBSoundtrack.#observer.observe(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(guz013): Handle if element is moved, it's group should be updated
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @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 {Element} el
|
||||||
|
* @returns {Element[]}
|
||||||
|
*/
|
||||||
|
function getAllDescendants(el) {
|
||||||
|
return Array.from(el.children).reduce(
|
||||||
|
(acc, current) => {
|
||||||
|
acc.push(current);
|
||||||
|
return acc.concat(getAllDescendants(current));
|
||||||
|
},
|
||||||
|
/** @type {Element[]} */ [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Readonly<Element>} el
|
* @param {Readonly<Element>} el
|
||||||
@@ -330,3 +751,4 @@ function getPercentageInView(element) {
|
|||||||
|
|
||||||
return Math.round((inView / globalThis.innerHeight) * 100);
|
return Math.round((inView / globalThis.innerHeight) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body xmlns:epub="http://www.idpf.org/2007/ops" class="body">
|
<body xmlns:epub="http://www.idpf.org/2007/ops" class="body">
|
||||||
<ipub-body style="--ipub-padding: 10%;">
|
<ipub-body style="--ipub-padding: 10%;" debug="">
|
||||||
<ipub-cover>
|
<ipub-cover>
|
||||||
<dialog>
|
<dialog>
|
||||||
<header>
|
<header>
|
||||||
@@ -24,49 +24,126 @@
|
|||||||
</dialog>
|
</dialog>
|
||||||
</ipub-cover>
|
</ipub-cover>
|
||||||
<main id="content">
|
<main id="content">
|
||||||
<ipub-background id="background0001">
|
<ipub-track>
|
||||||
<img src="../images/background0001.jpg" width="100" height="100" />
|
<ipub-track-item>
|
||||||
</ipub-background>
|
<ipub-track-item-position style="--ipub-track-item-position: 0%;" />
|
||||||
<ipub-image>
|
<ipub-soundtrack style="--ipub-color: cyan">
|
||||||
<img src="../images/image0001.png" />
|
<figure>
|
||||||
<ipub-interaction style="--ipub-y:88.5%;--ipub-x:6%" circle="">
|
<label>
|
||||||
<a href="https://krita.org" referrerpolicy="same-origin"
|
<input type="checkbox" />
|
||||||
rel="external nofollow noopener noreferrer" target="_blank" />
|
<figcaption>Soundtrack 1</figcaption>
|
||||||
</ipub-interaction>
|
</label>
|
||||||
<ipub-interaction style="--ipub-y:93.5%;--ipub-x:81.5%;--ipub-size:13%;">
|
<ipub-audio>
|
||||||
<a href="https://guz.one" referrerpolicy="same-origin"
|
<audio controls="true" volume="0" controlslist="nofullscreen"
|
||||||
rel="external nofollow noopener noreferrer" target="_blank" />
|
disableremoteplayback="true">
|
||||||
</ipub-interaction>
|
<source src="../audios/track1.webm" />
|
||||||
</ipub-image>
|
</audio>
|
||||||
<ipub-image>
|
</ipub-audio>
|
||||||
<img src="../images/image0002.png" />
|
</figure>
|
||||||
</ipub-image>
|
</ipub-soundtrack>
|
||||||
<ipub-background id="background0002">
|
</ipub-track-item>
|
||||||
<picture>
|
<ipub-track-item>
|
||||||
<img src="../images/background0002.jpg" />
|
<ipub-track-item-position style="--ipub-track-item-position: 50%;" />
|
||||||
</picture>
|
<ipub-soundtrack style="--ipub-color: green">
|
||||||
</ipub-background>
|
<figure>
|
||||||
<ipub-image>
|
<label>
|
||||||
<img src="../images/image0003.png" />
|
<input type="checkbox" />
|
||||||
</ipub-image>
|
<figcaption>Soundtrack 2</figcaption>
|
||||||
<ipub-image>
|
</label>
|
||||||
<img src="../images/image0004.png" />
|
<ipub-audio>
|
||||||
</ipub-image>
|
<audio controls="true" volume="0" controlslist="nofullscreen"
|
||||||
<ipub-background id="background0003">
|
disableremoteplayback="true">
|
||||||
<picture>
|
<source src="../audios/track2.webm" />
|
||||||
<img src="../images/background0003.jpg" />
|
</audio>
|
||||||
</picture>
|
</ipub-audio>
|
||||||
</ipub-background>
|
</figure>
|
||||||
<ipub-image>
|
</ipub-soundtrack>
|
||||||
<img src="../images/image0002.png" />
|
</ipub-track-item>
|
||||||
</ipub-image>
|
</ipub-track>
|
||||||
<ipub-image>
|
<ipub-background id="background0001">
|
||||||
<img src="../images/image0003.png" />
|
<img src="../images/background0001.jpg" width="100" height="100" />
|
||||||
</ipub-image>
|
</ipub-background>
|
||||||
<ipub-image>
|
<!-- <ipub-soundtrack style="==ipub-color:cyan"> -->
|
||||||
<img src="../images/image0004.png" />
|
<!-- <!== TODO: Search on how to make this more accessible, more semantic as using <details> -->-->
|
||||||
</ipub-image>
|
|
||||||
</main>
|
<!-- <figure> -->
|
||||||
|
<!-- <label> -->
|
||||||
|
<!-- <input type="checkbox" /> -->
|
||||||
|
<!-- <figcaption>Soundtrack 1</figcaption> -->
|
||||||
|
<!-- </label> -->
|
||||||
|
<!-- <ipub-audio> -->
|
||||||
|
<!-- <audio controls="true" volume="0" controlslist="nofullscreen" -->
|
||||||
|
<!-- disableremoteplayback=""> -->
|
||||||
|
<!-- <source src="../audios/track1.webm" /> -->
|
||||||
|
<!-- </audio> -->
|
||||||
|
<!-- </ipub-audio> -->
|
||||||
|
<!-- </figure> -->
|
||||||
|
<!-- </ipub-soundtrack> -->
|
||||||
|
<ipub-image>
|
||||||
|
<img src="../images/image0001.png" />
|
||||||
|
<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>
|
||||||
|
<ipub-image>
|
||||||
|
<img src="../images/image0002.png" />
|
||||||
|
</ipub-image>
|
||||||
|
<!-- <ipub-soundtrack style="==ipub-color:green;"> -->
|
||||||
|
<!-- <figure> -->
|
||||||
|
<!-- <label> -->
|
||||||
|
<!-- <input type="checkbox" /> -->
|
||||||
|
<!-- <figcaption>Soundtrack 2</figcaption> -->
|
||||||
|
<!-- </label> -->
|
||||||
|
<!-- <ipub-audio> -->
|
||||||
|
<!-- <audio controls="true" volume="0" controlslist="nofullscreen" disableremoteplayback=""> -->
|
||||||
|
<!-- <source src="../audios/track2.webm" /> -->
|
||||||
|
<!-- </audio> -->
|
||||||
|
<!-- </ipub-audio> -->
|
||||||
|
<!-- </figure> -->
|
||||||
|
<!-- </ipub-soundtrack> -->
|
||||||
|
<ipub-background id="background0002">
|
||||||
|
<picture>
|
||||||
|
<img src="../images/background0002.jpg" />
|
||||||
|
</picture>
|
||||||
|
</ipub-background>
|
||||||
|
<ipub-image>
|
||||||
|
<img src="../images/image0003.png" />
|
||||||
|
</ipub-image>
|
||||||
|
<ipub-image>
|
||||||
|
<img src="../images/image0004.png" />
|
||||||
|
</ipub-image>
|
||||||
|
<ipub-background id="background0003">
|
||||||
|
<picture>
|
||||||
|
<img src="../images/background0003.jpg" />
|
||||||
|
</picture>
|
||||||
|
</ipub-background>
|
||||||
|
<ipub-image>
|
||||||
|
<img src="../images/image0002.png" />
|
||||||
|
</ipub-image>
|
||||||
|
<!-- <ipub-soundtrack style="==ipub-color:yellow;"> -->
|
||||||
|
<!-- <figure> -->
|
||||||
|
<!-- <label> -->
|
||||||
|
<!-- <input type="checkbox" /> -->
|
||||||
|
<!-- <figcaption>Soundtrack 3</figcaption> -->
|
||||||
|
<!-- </label> -->
|
||||||
|
<!-- <ipub-audio> -->
|
||||||
|
<!-- <audio controls="true" volume="0" controlslist="nofullscreen" disableremoteplayback=""> -->
|
||||||
|
<!-- <source src="../audios/track3.webm" /> -->
|
||||||
|
<!-- </audio> -->
|
||||||
|
<!-- </ipub-audio> -->
|
||||||
|
<!-- </figure> -->
|
||||||
|
<!-- </ipub-soundtrack> -->
|
||||||
|
<ipub-image>
|
||||||
|
<img src="../images/image0003.png" />
|
||||||
|
</ipub-image>
|
||||||
|
<ipub-image>
|
||||||
|
<img src="../images/image0004.png" />
|
||||||
|
</ipub-image></main>
|
||||||
</ipub-body>
|
</ipub-body>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -12,7 +12,54 @@
|
|||||||
overflow: clip;
|
overflow: clip;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--z-controls: 10;
|
||||||
--z-cover: 9;
|
--z-cover: 9;
|
||||||
|
--z-overlays: 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[debug] {
|
||||||
|
--ipub-debug-bg-opacity: 30%;
|
||||||
|
background-color: rgba(
|
||||||
|
from var(--ipub-debug-color) r g b / var(--ipub-debug-bg-opacity, 30%)
|
||||||
|
);
|
||||||
|
outline-color: var(--ipub-debug-color);
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipub-track {
|
||||||
|
z-index: 1000;
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
ipub-track-item {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
*:not(ipub-offset) {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipub-track-item-position {
|
||||||
|
width: 5rem;
|
||||||
|
display: inline-block;
|
||||||
|
height: var(--ipub-track-item-position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipub-cover > dialog[open] {
|
ipub-cover > dialog[open] {
|
||||||
--ipub-accent-color: #fff;
|
--ipub-accent-color: #fff;
|
||||||
z-index: var(--z-cover);
|
z-index: var(--z-cover);
|
||||||
@@ -64,12 +111,14 @@ ipub-body {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
& > *:first-child:not(ipub-background),
|
& > *:first-child:not(ipub-background):not(ipub-soundtrack),
|
||||||
& > ipub-background:first-child + *:first-of-type {
|
& > ipub-background:first-of-type + *:first-of-type:not(ipub-soundtrack),
|
||||||
|
& > ipub-soundtrack:first-of-type + *:first-of-type:not(ipub-background) {
|
||||||
margin-top: var(--ipub-padding-t);
|
margin-top: var(--ipub-padding-t);
|
||||||
|
margin-bottom: calc(var(--ipub-gap) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > *:not(ipub-background) {
|
& > *:not(ipub-background):not(ipub-soundtrack) {
|
||||||
margin-top: calc(var(--ipub-gap) / 2);
|
margin-top: calc(var(--ipub-gap) / 2);
|
||||||
margin-right: var(--ipub-padding-r);
|
margin-right: var(--ipub-padding-r);
|
||||||
margin-left: var(--ipub-padding-l);
|
margin-left: var(--ipub-padding-l);
|
||||||
@@ -77,43 +126,151 @@ ipub-body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > *:last-child:not(ipub-background),
|
& > *:last-child:not(ipub-background),
|
||||||
& > ipub-background:last-child + *:last-of-type {
|
& > *:last-child:not(ipub-soundtrack),
|
||||||
|
& > ipub-background:last-of-type + *:last-of-type:not(ipub-soundtrack),
|
||||||
|
& > ipub-soundtrack:last-of-type + *:last-of-type:not(ipub-background) {
|
||||||
|
margin-top: calc(var(--ipub-gap) / 2);
|
||||||
margin-bottom: var(--ipub-padding-b);
|
margin-bottom: var(--ipub-padding-b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipub-soundtrack {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
z-index: var(--z-overlays);
|
||||||
|
|
||||||
|
--ipub-color: red;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
position: sticky;
|
||||||
|
align-self: start;
|
||||||
|
|
||||||
|
border-top: 0.1rem dashed var(--ipub-color);
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0;
|
||||||
|
height: 1.5rem;
|
||||||
|
font-size: small;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
label {
|
||||||
|
background-color: var(--ipub-color);
|
||||||
|
border-end-end-radius: 0.5rem;
|
||||||
|
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
width: max-content;
|
||||||
|
max-width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(input:checked) label {
|
||||||
|
border-end-end-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption::before {
|
||||||
|
content: "0 "; /* TODO: change to an icon and better positioning */
|
||||||
|
}
|
||||||
|
|
||||||
|
figcaption::after {
|
||||||
|
content: " >"; /* TODO: change to an icon and better positioning */
|
||||||
|
}
|
||||||
|
&:has(input:checked) figcaption::after {
|
||||||
|
content: " <"; /* TODO: change to an icon and better positioning */
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(input) audio {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(input:checked) audio {
|
||||||
|
width: 100%;
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
background-color: var(--ipub-color);
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
border-end-end-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
audio::-webkit-media-controls-enclosure {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
audio::-webkit-media-controls-panel {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[playing] figure figcaption::before {
|
||||||
|
content: "P "; /* TODO: change to an icon and better positioning */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipub-background {
|
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-width: 100vw;
|
||||||
--ipub-height: 100vh;
|
--ipub-height: 100vh;
|
||||||
|
|
||||||
&[sticky] {
|
display: inline-block;
|
||||||
display: inline-block;
|
top: 0;
|
||||||
top: 0;
|
left: 0;
|
||||||
left: 0;
|
width: 0;
|
||||||
width: 0;
|
height: 0;
|
||||||
height: 0;
|
position: sticky;
|
||||||
position: sticky;
|
align-self: start;
|
||||||
align-self: start;
|
|
||||||
|
&:first-of-type,
|
||||||
|
&[nofade] {
|
||||||
|
--ipub-mask: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[fade] img {
|
img {
|
||||||
/* For testing */
|
/* For testing */
|
||||||
/* background-image: linear-gradient( */
|
/* background-image: linear-gradient( */
|
||||||
/* rgba(266, 0, 0, 1) 0%, */
|
/* rgba(266, 0, 0, 1) 0%, */
|
||||||
/* rgba(0, 266, 0, 1) calc(100% + calc(var(--ipub-fade, 100%) * -1)), */
|
/* rgba(0, 266, 0, 1) calc(100% + calc(var(--ipub-fade, 100%) * -1)), */
|
||||||
/* rgba(266, 0, 266, 1) 100% */
|
/* rgba(266, 0, 266, 1) 100% */
|
||||||
/* ) !important; */
|
/* ) !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); */
|
/* background-image: var(--mask); */
|
||||||
mask-image: var(--mask);
|
mask-image: var(--ipub-mask);
|
||||||
-webkit-mask-image: var(--mask);
|
-webkit-mask-image: var(--ipub-mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > picture {
|
& > picture {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
display: block;
|
display: block;
|
||||||
width: var(--ipub-width);
|
width: var(--ipub-width);
|
||||||
height: var(--ipub-height);
|
height: var(--ipub-height);
|
||||||
@@ -125,6 +282,9 @@ ipub-background {
|
|||||||
}
|
}
|
||||||
/* Support standalone img element */
|
/* Support standalone img element */
|
||||||
& > img {
|
& > img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
display: block;
|
display: block;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
width: var(--ipub-width);
|
width: var(--ipub-width);
|
||||||
|
|||||||
Reference in New Issue
Block a user