From f63683dad80437834ee16e89478519a812ced372 Mon Sep 17 00:00:00 2001 From: QuarkPixel Date: Thu, 18 Sep 2025 14:48:01 +0800 Subject: feat(config): make libra more customizable through config.js and libra.css --- src/config.js | 6 ++++-- src/libra.css | 14 ++++++++++++-- src/lightbox.js | 4 ++-- src/overlay.js | 20 +++++++++----------- src/shadow.js | 6 +++--- 5 files changed, 30 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/config.js b/src/config.js index eea8314..c306d8f 100644 --- a/src/config.js +++ b/src/config.js @@ -1,8 +1,10 @@ const config = { selector: '[data-libra]', margin: 50, - offset: { x: 0, y: 0 } -} + offset: { x: 0, y: 0 }, + transitionDuration: 200, // milliseconds + imageCursor: 'zoom-in', +}; function deepMerge(target, source) { for (const key in source) { diff --git a/src/libra.css b/src/libra.css index 1c7277c..f3d6e7b 100644 --- a/src/libra.css +++ b/src/libra.css @@ -5,7 +5,7 @@ width: 100%; height: 100%; padding: 1rem; - transition: opacity 0.2s ease-in-out; + transition: opacity ease-in-out; backdrop-filter: blur(2rem); background-color: rgba(255, 255, 255, 0.75); @@ -17,6 +17,16 @@ cursor: pointer; } +.libra-overlay.libra-visible { + opacity: 1; + z-index: 900; +} + +.libra-overlay.libra-hidden { + opacity: 0; + z-index: -100; +} + @media (prefers-color-scheme: dark) { .libra-overlay { background-color: rgba(0, 0, 0, 0.75); @@ -25,9 +35,9 @@ .libra-shadow { position: absolute; - transition-duration: 0.2s; transition-property: transform; cursor: pointer; + z-index: 1000; } .libra-shadow.open { diff --git a/src/lightbox.js b/src/lightbox.js index bef29eb..7bf23d9 100644 --- a/src/lightbox.js +++ b/src/lightbox.js @@ -33,8 +33,8 @@ export default class LightBox { this.images.forEach(image => { if (image.tagName !== 'IMG') return; image.id = crypto.randomUUID(); // generate unique id for each image - image.style.cursor = 'zoom-in'; - image.addEventListener('click', () => this.open(image)); // click image to open lightbox + image.style.cursor = config.imageCursor; + image.addEventListener('click', () => this.open(image)); // click image to open lightbox }); } diff --git a/src/overlay.js b/src/overlay.js index 613340c..78fb579 100644 --- a/src/overlay.js +++ b/src/overlay.js @@ -1,15 +1,13 @@ -export default class Overlay { - zIndex = 900; - zIndexHidden = -100; +import config from './config'; +export default class Overlay { constructor() { const overlay = document.createElement('div'); overlay.id = 'libra-overlay'; - overlay.classList.add('libra-overlay'); + overlay.classList.add('libra-overlay', 'libra-hidden'); - // initialized state - overlay.style.zIndex = this.zIndexHidden; - overlay.style.opacity = '0'; + // Set transition duration from config + overlay.style.transitionDuration = config.transitionDuration + 'ms'; this.element = overlay; document.body.appendChild(overlay); @@ -18,12 +16,12 @@ export default class Overlay { } show() { - this.element.style.opacity = '1'; - this.element.style.zIndex = this.zIndex; + this.element.classList.remove('libra-hidden'); + this.element.classList.add('libra-visible'); } hide() { - this.element.style.opacity = '0'; - this.element.style.zIndex = '-100'; + this.element.classList.remove('libra-visible'); + this.element.classList.add('libra-hidden'); } } diff --git a/src/shadow.js b/src/shadow.js index 9889240..e36affa 100644 --- a/src/shadow.js +++ b/src/shadow.js @@ -2,8 +2,7 @@ import Animation from './animation.js' import config from './config' export default class Shadow { - zIndex = '1000' - isOpen = false + isOpen = false; /** * ============ @@ -39,7 +38,8 @@ export default class Shadow { shadow.style.left = `${this.originalPosition.left}px`; shadow.style.width = `${this.originalPosition.width}px`; shadow.style.height = `${this.originalPosition.height}px`; - shadow.style.zIndex = this.zIndex; + // Set transition duration from config + shadow.style.transitionDuration = config.transitionDuration + 'ms'; document.body.appendChild(shadow); this.element = shadow; -- cgit v1.2.3 From b66d25addeefbc60b01adbbf86fa2272700266fe Mon Sep 17 00:00:00 2001 From: QuarkPixel Date: Thu, 18 Sep 2025 14:49:16 +0800 Subject: fix: viewport change causes animation position error --- src/shadow.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'src') diff --git a/src/shadow.js b/src/shadow.js index e36affa..be7eb9c 100644 --- a/src/shadow.js +++ b/src/shadow.js @@ -51,10 +51,27 @@ export default class Shadow { * ============ */ + updateOriginalPosition() { + const rect = this.original.getBoundingClientRect(); + this.originalPosition = { + top: rect.top + window.scrollY + config.offset.y, + left: rect.left + window.scrollX + config.offset.x, + width: rect.width, + height: rect.height, + }; + } + open() { this.isOpen = true; this.element.classList.add('open'); + // Update original position before animation in case viewport changed + this.updateOriginalPosition(); + this.element.style.top = `${this.originalPosition.top}px`; + this.element.style.left = `${this.originalPosition.left}px`; + this.element.style.width = `${this.originalPosition.width}px`; + this.element.style.height = `${this.originalPosition.height}px`; + this.original.style.visibility = 'hidden'; if (!document.body.contains(this.element)) this.placeItself(); -- cgit v1.2.3 From 755f69bcbd80a0e2fd433a8ccd1427d856cda946 Mon Sep 17 00:00:00 2001 From: QuarkPixel Date: Thu, 18 Sep 2025 14:50:35 +0800 Subject: fix: display nothing when image not fully loaded --- src/shadow.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/shadow.js b/src/shadow.js index be7eb9c..5435532 100644 --- a/src/shadow.js +++ b/src/shadow.js @@ -75,11 +75,26 @@ export default class Shadow { this.original.style.visibility = 'hidden'; if (!document.body.contains(this.element)) this.placeItself(); - Animation.transformMatrix( - this.element, - this.originalPosition, - Animation.calculateFinalState(this.element) - ); + // Wait for image to load before animating + if (this.element.naturalWidth === 0 || this.element.naturalHeight === 0) { + this.element.addEventListener( + 'load', + () => { + Animation.transformMatrix( + this.element, + this.originalPosition, + Animation.calculateFinalState(this.element), + ); + }, + { once: true }, + ); + } else { + Animation.transformMatrix( + this.element, + this.originalPosition, + Animation.calculateFinalState(this.element), + ); + } } close() { -- cgit v1.2.3 From 461b268e16f4b9369637da17afba1c814726b58f Mon Sep 17 00:00:00 2001 From: QuarkPixel Date: Thu, 18 Sep 2025 14:51:09 +0800 Subject: fix: missing transition when closing --- src/shadow.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/shadow.js b/src/shadow.js index 5435532..fd3c2f3 100644 --- a/src/shadow.js +++ b/src/shadow.js @@ -101,10 +101,12 @@ export default class Shadow { this.isOpen = false; this.element.classList.remove('open'); - this.original.style.visibility = 'visible'; - Animation.resetTransformMatrix(this.element); - setTimeout(() => this.destroyItself(), 300); + // Wait for animation to complete before showing original and destroying shadow + setTimeout(() => { + this.original.style.visibility = 'visible'; + this.destroyItself(); + }, config.transitionDuration); } placeItself() { -- cgit v1.2.3 From 6c3e6e0e3f1a96e92299747d2ec5f552908728cb Mon Sep 17 00:00:00 2001 From: Eltrac Date: Thu, 18 Sep 2025 18:27:37 +0800 Subject: style: consistent unit assignment --- src/shadow.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/shadow.js b/src/shadow.js index fd3c2f3..4e06263 100644 --- a/src/shadow.js +++ b/src/shadow.js @@ -34,10 +34,10 @@ export default class Shadow { shadow.classList.add('libra-shadow'); // style and position shadow image - shadow.style.top = `${this.originalPosition.top}px`; - shadow.style.left = `${this.originalPosition.left}px`; - shadow.style.width = `${this.originalPosition.width}px`; - shadow.style.height = `${this.originalPosition.height}px`; + shadow.style.top = this.originalPosition.top + 'px'; + shadow.style.left = this.originalPosition.left + 'px'; + shadow.style.width = this.originalPosition.width + 'px'; + shadow.style.height = this.originalPosition.height + 'px'; // Set transition duration from config shadow.style.transitionDuration = config.transitionDuration + 'ms'; -- cgit v1.2.3 From f383f1cecc713ae5f245cc61a825bd9db9db39bb Mon Sep 17 00:00:00 2001 From: Eltrac Date: Thu, 18 Sep 2025 18:41:51 +0800 Subject: refactor: avoid repeating animation logic --- src/shadow.js | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/shadow.js b/src/shadow.js index 4e06263..b02fad5 100644 --- a/src/shadow.js +++ b/src/shadow.js @@ -75,25 +75,20 @@ export default class Shadow { this.original.style.visibility = 'hidden'; if (!document.body.contains(this.element)) this.placeItself(); - // Wait for image to load before animating - if (this.element.naturalWidth === 0 || this.element.naturalHeight === 0) { - this.element.addEventListener( - 'load', - () => { - Animation.transformMatrix( - this.element, - this.originalPosition, - Animation.calculateFinalState(this.element), - ); - }, - { once: true }, - ); - } else { + // play animation + const animate = () => Animation.transformMatrix( this.element, this.originalPosition, Animation.calculateFinalState(this.element), ); + + if (this.element.naturalWidth === 0 || this.element.naturalHeight === 0) { + // Wait for image to load before animating + this.element.addEventListener('load', animate, { once: true }); + } else { + // animate directly if loaded + animate(); } } -- cgit v1.2.3 From 7113655e17a38d9d73cffe469c28b6ddf5b0636f Mon Sep 17 00:00:00 2001 From: Eltrac Date: Thu, 18 Sep 2025 18:51:39 +0800 Subject: refactor: avoid repetition Since we are constantly adjusting shadow's positions and fetching original positions, encapsulate them into functions. --- src/shadow.js | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/shadow.js b/src/shadow.js index b02fad5..50e3345 100644 --- a/src/shadow.js +++ b/src/shadow.js @@ -12,15 +12,7 @@ export default class Shadow { constructor(image) { this.original = image; - - const rect = this.original.getBoundingClientRect(); - this.originalPosition = { - top: rect.top + window.scrollY + config.offset.y, - left: rect.left + window.scrollX + config.offset.x, - width: rect.width, - height: rect.height - } - + this.fetchOriginalPosition(); this.create(image); return this; @@ -32,26 +24,16 @@ export default class Shadow { shadow.src = image.src; shadow.id = `libra-shadow-${image.id}`; shadow.classList.add('libra-shadow'); - - // style and position shadow image - shadow.style.top = this.originalPosition.top + 'px'; - shadow.style.left = this.originalPosition.left + 'px'; - shadow.style.width = this.originalPosition.width + 'px'; - shadow.style.height = this.originalPosition.height + 'px'; - // Set transition duration from config shadow.style.transitionDuration = config.transitionDuration + 'ms'; - document.body.appendChild(shadow); this.element = shadow; - } - /** - * ============ - * Actions - * ============ - */ + // set positioning and place shadow element + this.positionShadow(); + this.placeItself(); + } - updateOriginalPosition() { + fetchOriginalPosition() { const rect = this.original.getBoundingClientRect(); this.originalPosition = { top: rect.top + window.scrollY + config.offset.y, @@ -61,16 +43,26 @@ export default class Shadow { }; } + positionShadow() { + this.element.style.top = this.originalPosition.top + 'px'; + this.element.style.left = this.originalPosition.left + 'px'; + this.element.style.width = this.originalPosition.width + 'px'; + this.element.style.height = this.originalPosition.height + 'px'; + } + + /** + * ============ + * Actions + * ============ + */ + open() { this.isOpen = true; this.element.classList.add('open'); // Update original position before animation in case viewport changed - this.updateOriginalPosition(); - this.element.style.top = `${this.originalPosition.top}px`; - this.element.style.left = `${this.originalPosition.left}px`; - this.element.style.width = `${this.originalPosition.width}px`; - this.element.style.height = `${this.originalPosition.height}px`; + this.fetchOriginalPosition(); + this.positionShadow(); this.original.style.visibility = 'hidden'; if (!document.body.contains(this.element)) this.placeItself(); -- cgit v1.2.3