feat(ipub): sticky background implementation via web components
This commit is contained in:
@@ -1,8 +1,111 @@
|
||||
"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
|
||||
* @implements {IPUBElementOnScreen}
|
||||
*/
|
||||
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 !== IPUBBackground.elementName) {
|
||||
instance = instance.parentElement;
|
||||
}
|
||||
|
||||
if (instance.tagName !== IPUBBackground.elementName) {
|
||||
console.error(
|
||||
"IPUBBackground: malformed <ipub-background> element",
|
||||
e.target,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.intersectionRatio > 0) {
|
||||
instancesOnScreen.set(instance.id, instance);
|
||||
} else {
|
||||
instancesOnScreen.delete(instance.id);
|
||||
}
|
||||
}),
|
||||
);
|
||||
})();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (!this.id) {
|
||||
console.warn(
|
||||
`IPUB: ipub-background has not ID, assigning one based on innerHTML`,
|
||||
this,
|
||||
);
|
||||
this.id = hashString(this.innerHTML);
|
||||
}
|
||||
console.debug(`IPUB: Added ipub-background#${this.id} to page`);
|
||||
|
||||
const image = this.querySelector("img");
|
||||
if (this.hasAttribute("fade") && image) {
|
||||
console.debug(`IPUB: Added ipub-background#${this.id} to observer`);
|
||||
|
||||
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(`${this.id} is ${perc} on screen`);
|
||||
|
||||
if (!this.style.getPropertyValue("--ipub-fade")) {
|
||||
this.style.setProperty("--ipub-fade", `${perc}%`);
|
||||
} else 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();
|
||||
|
||||
|
||||
@@ -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">
|
||||
@@ -10,6 +10,9 @@
|
||||
</head>
|
||||
<body xmlns:epub="http://www.idpf.org/2007/ops" class="body">
|
||||
<main data-ipub-element="content">
|
||||
<ipub-background id="background0001" sticky="">
|
||||
<img src="../images/background0001.jpg" width="100" height="100" />
|
||||
</ipub-background>
|
||||
<section data-ipub-element="page" id="page01">
|
||||
<span data-ipub-element="image">
|
||||
<img src="../images/image0001.png" />
|
||||
@@ -77,6 +80,11 @@
|
||||
<source src="../audios/audio0001.wav.disable" />
|
||||
</audio>
|
||||
</section>
|
||||
<ipub-background sticky="" fade="" 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" />
|
||||
|
||||
@@ -1,22 +1,102 @@
|
||||
.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"] {
|
||||
[data-ipub-element="content"] {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[data-ipub-element="content"] > [data-ipub-element="page"] {
|
||||
margin: 5% 10%;
|
||||
}
|
||||
|
||||
ipub-background[sticky],
|
||||
[data-ipub-element="sticky-background"] {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: sticky;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
ipub-background {
|
||||
--ipub-width: 100vw;
|
||||
--ipub-height: 100vh;
|
||||
|
||||
&[sticky] {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[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 +124,4 @@ a[data-ipub-element="interaction"] {
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user