summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEltrac <hi@guhub.cn>2025-09-15 10:10:29 +0800
committerEltrac <hi@guhub.cn>2025-09-15 10:10:29 +0800
commit4cb9d21fb8860d3401db5d94d30c309385599a2e (patch)
tree08bd2cb39bc2602fec302c78f3990edfd11a93b1
initial commit
-rw-r--r--index.html20
-rw-r--r--src/libo.css30
-rw-r--r--src/lightbox.js51
-rw-r--r--src/overlay.js35
-rw-r--r--src/shadow.js91
-rw-r--r--src/utility.js12
-rw-r--r--test.jpgbin0 -> 3236769 bytes
7 files changed, 239 insertions, 0 deletions
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..59aa061
--- /dev/null
+++ b/index.html
@@ -0,0 +1,20 @@
+<html>
+ <head>
+ <link rel="stylesheet" href="/src/libo.css" />
+ </head>
+ <body>
+ <div class="libo">
+ <img
+ style="width:20%"
+ data-libo
+ src="test.jpg"
+ />
+ </div>
+ <script type="module">
+ import LightBox from './src/lightbox.js';
+ const lightbox = new LightBox({
+ container: '.libo'
+ });
+ </script>
+ </body>
+</html>
diff --git a/src/libo.css b/src/libo.css
new file mode 100644
index 0000000..f495ed1
--- /dev/null
+++ b/src/libo.css
@@ -0,0 +1,30 @@
+.libo-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: #fff;
+ transition: opacity 0.3s ease-in-out;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.libo-shadow {
+ position: absolute;
+ transition-duration: 0.3s;
+ transition-property: transform;
+ display: block;
+}
+
+.libo-shadow.zoomed {
+ position: static;
+ margin: 0 auto;
+ max-width: 100%;
+ width: auto;
+ height: auto;
+
+ box-shadow: 0 0 1rem rgba(0, 0, 0, 0.3);
+}
diff --git a/src/lightbox.js b/src/lightbox.js
new file mode 100644
index 0000000..60e8518
--- /dev/null
+++ b/src/lightbox.js
@@ -0,0 +1,51 @@
+import utility from './utility.js';
+import Overlay from './overlay.js';
+import Shadow from './shadow.js';
+
+export default class LightBox {
+ overlay = new Overlay();
+
+ constructor({ container }) {
+ this.container = container;
+ this.init();
+ }
+
+ init() {
+ const container = document.querySelectorAll(this.container);
+ const images = document.querySelectorAll(`${this.container} img[data-libo]`);
+
+ images.forEach(image => {
+ // generate unique id for image
+ const id = utility.generateUniqueId(image.src);
+ image.id = id;
+
+ // get parent and wrap image with a link if it's not already wrapped
+ const parent = image.parentElement;
+ let link;
+ if (!parent || parent.tagName !== 'A') {
+ link = document.createElement('a');
+ link.href = image.src;
+ link.dataset.liboLink = true;
+
+ parent.insertBefore(link, image);
+ link.appendChild(image);
+ } else {
+ link = parent;
+ }
+
+ // add event listener to open lightbox on click
+ link.addEventListener('click', e => {
+ e.preventDefault();
+ this.openLightBox(image);
+ });
+
+ });
+ }
+
+ openLightBox(image) {
+ const shadow = new Shadow(image);
+ this.overlay.add(shadow);
+ this.overlay.show();
+ shadow.zoom();
+ }
+}
diff --git a/src/overlay.js b/src/overlay.js
new file mode 100644
index 0000000..c5dd853
--- /dev/null
+++ b/src/overlay.js
@@ -0,0 +1,35 @@
+export default class Overlay {
+ zIndex = 900;
+ zIndexHidden = -100;
+
+ constructor() {
+ const overlay = document.createElement('div');
+ overlay.id = 'libo-overlay';
+ overlay.classList.add('libo-overlay');
+
+ // initialized state
+ overlay.style.zIndex = this.zIndexHidden;
+ overlay.style.opacity = '0';
+
+ this.element = overlay;
+ document.body.appendChild(overlay);
+
+ return this;
+ }
+
+ add(element) {
+ this.element.innerHTML = '';
+ this.element.appendChild(element.element || element);
+ return this;
+ }
+
+ show() {
+ this.element.style.opacity = '1';
+ this.element.style.zIndex = this.zIndex;
+ }
+
+ hide() {
+ this.element.style.opacity = '0';
+ this.element.style.zIndex = '-100';
+ }
+}
diff --git a/src/shadow.js b/src/shadow.js
new file mode 100644
index 0000000..f4964ad
--- /dev/null
+++ b/src/shadow.js
@@ -0,0 +1,91 @@
+export default class Shadow {
+ zIndex = '100'
+
+ constructor(image) {
+ // create shadow image
+ const shadowImage = document.createElement('img');
+ shadowImage.src = image.src;
+ shadowImage.id = `libo-shadow-${image.id}`;
+ shadowImage.classList.add('libo-shadow');
+
+ // style and position shadow image
+ shadowImage.style.top = image.offsetTop;
+ shadowImage.style.left = image.offsetLeft;
+ shadowImage.style.width = image.offsetWidth;
+ shadowImage.style.height = image.offsetHeight;
+ shadowImage.style.zIndex = this.zIndex;
+
+ // append shadow image to body
+ document.body.appendChild(shadowImage);
+
+ this.element = shadowImage;
+ return this;
+ }
+
+ zoom() {
+ // get starting state
+ const startingState = {
+ top: this.element.offsetTop,
+ left: this.element.offsetLeft,
+ width: this.element.offsetWidth,
+ height: this.element.offsetHeight
+ }
+
+ // temporarily finalize element
+ // and get final state
+ this.element.classList.add('zoomed');
+ this.element.style.width = 'unset';
+ this.element.style.height = 'unset';
+
+ const finalState = {
+ top: this.element.offsetTop,
+ left: this.element.offsetLeft,
+ width: this.element.offsetWidth,
+ height: this.element.offsetHeight
+ }
+
+ // re-initialize element and play animation
+ this.element.classList.remove('zoomed');
+ this.animate(startingState, finalState)
+ }
+
+ animate(startingState, finalState) {
+ // Set up the interval for smooth transition
+ const steps = 20; // Number of steps for smoothness
+ let step = 0;
+
+ // Using requestAnimationFrame for smoother animation
+ const frame = () => {
+ // Calculate the progress (from 0 to 1)
+ const progress = step / steps;
+
+ // Interpolate the current values based on progress
+ const currentTop = startingState.top + (finalState.top - startingState.top) * progress;
+ const currentLeft = startingState.left + (finalState.left - startingState.left) * progress;
+ const currentWidth = startingState.width + (finalState.width - startingState.width) * progress;
+ const currentHeight = startingState.height + (finalState.height - startingState.height) * progress;
+
+ // Update the element's style
+ this.element.style.top = `${currentTop}px`;
+ this.element.style.left = `${currentLeft}px`;
+ this.element.style.width = `${currentWidth}px`;
+ this.element.style.height = `${currentHeight}px`;
+
+ // Log current size for debugging
+ console.log(`Step ${step}: ${currentWidth}, ${currentHeight}`);
+
+ // Increase the step count
+ step++;
+
+ // Continue animating until we reach the final step
+ if (step < steps) {
+ requestAnimationFrame(frame);
+ } else {
+ this.element.classList.add('zoomed');
+ }
+ };
+
+ // Start the animation
+ requestAnimationFrame(frame);
+ }
+}
diff --git a/src/utility.js b/src/utility.js
new file mode 100644
index 0000000..790443d
--- /dev/null
+++ b/src/utility.js
@@ -0,0 +1,12 @@
+const utility = {
+ generateUniqueId: (str) => {
+ const encoder = new TextEncoder();
+ const data = encoder.encode(str);
+ const hashBuffer = crypto.subtle.digest('SHA-256', data); // SHA-256 hash
+ const hashArray = Array.from(new Uint8Array(hashBuffer)); // Convert buffer to array
+ const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
+ return hashHex; // Return the hex string
+ }
+}
+
+export default utility;
diff --git a/test.jpg b/test.jpg
new file mode 100644
index 0000000..99de989
--- /dev/null
+++ b/test.jpg
Binary files differ