diff options
| -rw-r--r-- | index.html | 20 | ||||
| -rw-r--r-- | src/libo.css | 30 | ||||
| -rw-r--r-- | src/lightbox.js | 51 | ||||
| -rw-r--r-- | src/overlay.js | 35 | ||||
| -rw-r--r-- | src/shadow.js | 91 | ||||
| -rw-r--r-- | src/utility.js | 12 | ||||
| -rw-r--r-- | test.jpg | bin | 0 -> 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 Binary files differnew file mode 100644 index 0000000..99de989 --- /dev/null +++ b/test.jpg |
