docs: Update skeleton loader implementation from hyperscript to JavaScript
MIGRATION SUMMARY: - Moved skeleton loader logic from hyperscript to JavaScript (main.js) - Changed from htmx:oobAfterSwap to htmx:afterSettle event - Changed OOB swap from innerHTML to outerHTML for proper element replacement - Added languageSwitching flag for state tracking - Added 100ms delay after afterSettle for final render completion DOCUMENTATION UPDATES: - 2-MODERN-WEB-TECHNIQUES.md: Updated skeleton loader section with
This commit is contained in:
@@ -1930,31 +1930,57 @@ Time 600ms+: opacity=NaN ← Element destroyed!
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Language switch with skeleton loading
|
||||
htmx.on('htmx:beforeSwap', function(evt) {
|
||||
// Show skeletons BEFORE swap
|
||||
document.querySelectorAll('.component-wrapper').forEach(wrapper => {
|
||||
wrapper.classList.add('loading');
|
||||
});
|
||||
// static/js/main.js - Skeleton loader for language transitions
|
||||
let languageSwitching = false;
|
||||
|
||||
// Add .loading class when language button is clicked
|
||||
document.addEventListener('htmx:beforeRequest', function(evt) {
|
||||
const element = evt.detail.elt;
|
||||
if (element && element.classList && element.classList.contains('selector-btn')) {
|
||||
// Set flag to track language switching
|
||||
languageSwitching = true;
|
||||
|
||||
// Add loading class to page containers
|
||||
const page1 = document.getElementById('cv-inner-content-page-1');
|
||||
const page2 = document.getElementById('cv-inner-content-page-2');
|
||||
if (page1) page1.classList.add('loading');
|
||||
if (page2) page2.classList.add('loading');
|
||||
}
|
||||
});
|
||||
|
||||
htmx.on('htmx:afterSettle', function(evt) {
|
||||
// Hide skeletons AFTER content settles
|
||||
document.querySelectorAll('.component-wrapper').forEach(wrapper => {
|
||||
wrapper.classList.remove('loading');
|
||||
});
|
||||
// Remove .loading class after language transition completes
|
||||
document.addEventListener('htmx:afterSettle', function(evt) {
|
||||
if (languageSwitching) {
|
||||
// Wait for final render to complete
|
||||
setTimeout(function() {
|
||||
const page1 = document.getElementById('cv-inner-content-page-1');
|
||||
const page2 = document.getElementById('cv-inner-content-page-2');
|
||||
if (page1) page1.classList.remove('loading');
|
||||
if (page2) page2.classList.remove('loading');
|
||||
|
||||
// Reset flag
|
||||
languageSwitching = false;
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Architecture Pattern:**
|
||||
1. **User clicks language toggle** → HTMX `beforeSwap` event fires
|
||||
2. **JavaScript adds `.loading` class** → Triggers CSS transition (actual-content opacity: 1 → 0, skeleton-content opacity: 0 → 1)
|
||||
3. **Skeleton appears** → Smooth 250ms fade-in with shimmer animation
|
||||
4. **HTMX fetches new language content** → Server renders and returns HTML
|
||||
5. **HTMX swaps content** → New actual-content replaces old
|
||||
6. **afterSettle event fires** → JavaScript removes `.loading` class
|
||||
7. **Skeleton fades out** → Smooth 250ms fade (skeleton opacity: 1 → 0, actual-content opacity: 0 → 1)
|
||||
8. **Result** → Smooth, professional loading experience with zero layout shift
|
||||
1. **User clicks language button** → HTMX `htmx:beforeRequest` event fires
|
||||
2. **JavaScript detects `.selector-btn` click** → Sets `languageSwitching` flag
|
||||
3. **JavaScript adds `.loading` to parent containers** → Triggers CSS cascade to child `.component-wrapper` elements
|
||||
4. **Skeleton appears** → CSS transition (actual-content opacity: 1 → 0, skeleton-content opacity: 0 → 1) + shimmer animation
|
||||
5. **HTMX fetches new language content** → Server renders and returns HTML via OOB swaps
|
||||
6. **HTMX swaps content** → Out-of-band (OOB) swap replaces page containers
|
||||
7. **`htmx:afterSettle` event fires** → JavaScript waits 100ms for final render
|
||||
8. **JavaScript removes `.loading` class** → CSS transition reverses (skeleton opacity: 1 → 0, actual-content opacity: 0 → 1)
|
||||
9. **Result** → Smooth, professional loading experience with zero layout shift
|
||||
|
||||
**Why JavaScript Instead of Hyperscript:**
|
||||
- ✅ **Reliable Playwright testing** - JavaScript event handlers work consistently in automated tests
|
||||
- ✅ **Debugging** - Console.log statements provide clear execution tracking
|
||||
- ✅ **Maintainability** - Standard JavaScript patterns familiar to all developers
|
||||
- ✅ **Performance** - Direct DOM manipulation, no hyperscript parser overhead
|
||||
|
||||
**Benefits:**
|
||||
- ✅ **Zero layout shift** - Skeletons match exact dimensions of actual content
|
||||
@@ -1966,16 +1992,24 @@ htmx.on('htmx:afterSettle', function(evt) {
|
||||
- ✅ **Reusable** - Works for any HTMX swap operation, not just language switch
|
||||
|
||||
**Implementation Locations:**
|
||||
- **CSS:** `static/css/skeleton.css` (341 lines) - Complete skeleton system
|
||||
- **Template:** `templates/partials/skeleton-loader.html` - Skeleton placeholders
|
||||
- **Component wrappers:** Each CV section wrapped with `.component-wrapper`
|
||||
- **HTMX events:** `static/js/main.js` - beforeSwap/afterSettle listeners
|
||||
- **CSS:** `static/css/skeleton.css` - Complete skeleton system with shimmer animations
|
||||
- **JavaScript:** `static/js/main.js` (lines 231-273) - HTMX event handlers for skeleton control
|
||||
- **Templates:** `templates/partials/sections/header.html` - Component wrapper structure
|
||||
- **Page Containers:** `templates/cv-content.html` - Parent containers receiving `.loading` class
|
||||
- **Language Switch:** `templates/language-switch.html` - `.selector-btn` triggers skeleton display
|
||||
|
||||
**Testing:** Automated tests in `tests/mjs/12-skeleton-language-transitions.test.mjs` verify:
|
||||
- Skeletons appear during language switch
|
||||
- Content replaced without full page reload
|
||||
- Skeletons removed after content loads
|
||||
- Zero layout shift during transition
|
||||
- ✅ Component wrapper structure (dual-state: actual + skeleton content)
|
||||
- ✅ Skeleton CSS loaded (shimmer animation verified)
|
||||
- ✅ First language switch (EN → ES) - Loading class added/removed
|
||||
- ✅ Second language switch (ES → EN) - Consistent behavior
|
||||
- ✅ Third language switch (EN → ES) - Regression check
|
||||
- ✅ No stuck loading states (all containers clean after transition)
|
||||
- ✅ JavaScript event handlers configured (languageSwitching flag)
|
||||
|
||||
**Test Results:** 7/7 tests pass - Complete validation of skeleton loader functionality
|
||||
|
||||
**Run Test:** `bun tests/mjs/12-skeleton-language-transitions.test.mjs`
|
||||
|
||||
**Pixel-Perfect Matching:**
|
||||
|
||||
@@ -2354,7 +2388,7 @@ set #shortcuts-button's *zoom to inverseZoom
|
||||
3. `templates/partials/navigation/hamburger-menu.html`
|
||||
- Removed conflicting hyperscript from show zoom button
|
||||
|
||||
4. `MODERN-WEB-TECHNIQUES.md`
|
||||
4. `2-MODERN-WEB-TECHNIQUES.md`
|
||||
- Updated documentation to reflect fixes
|
||||
- Added technical lessons learned
|
||||
|
||||
|
||||
Reference in New Issue
Block a user