- Create internal/constants package with all hardcoded values (environment, cookies, themes, headers, routes, cache) - Create internal/httputil package for HTTP helper functions - Update all handlers and middleware to use centralized constants - Reorganize documentation with numbered prefixes (00-26) - Remove duplicate docs from validation folder and docs/ - Delete handlers/constants.go (moved to internal/constants)
8.6 KiB
Zoom Implementation - Technical Documentation
Overview
This document describes the technical implementation of the custom zoom feature in the CV application, including architectural decisions, constraints, and solutions to specific challenges.
Implementation Summary
- Range: 25% - 175% (centered at 100%)
- Technology: CSS
zoomproperty on wrapper element - Fixed Elements: Action bar, zoom control, info button, back-to-top button
- Viewport Behavior: Horizontal scroll enabled when zoomed > 100%
Technical Architecture
1. Core Zoom Application
Location: static/js/main.js - applyZoom() function
function applyZoom(zoomValue, saveToStorage = true) {
const zoomWrapper = document.getElementById('zoom-wrapper');
const zoomLevel = zoomValue / 100;
// Apply CSS zoom to wrapper
zoomWrapper.style.zoom = zoomLevel;
// Allow horizontal expansion when zoomed > 100%
if (zoomLevel > 1) {
zoomWrapper.style.minWidth = `${window.innerWidth}px`;
} else {
zoomWrapper.style.minWidth = '';
}
// Apply inverse zoom to fixed buttons
const inverseZoom = 1 / zoomLevel;
document.getElementById('back-to-top').style.zoom = inverseZoom;
document.getElementById('info-button').style.zoom = inverseZoom;
}
2. HTML Structure
Location: templates/index.html
<!-- Fixed elements (NOT affected by zoom) -->
<div class="action-bar">...</div>
<nav class="navigation-menu">...</nav>
<!-- Zoomable content wrapper -->
<div id="zoom-wrapper" class="zoom-wrapper">
<div class="cv-container">
<main class="cv-paper">
<!-- CV content here -->
</main>
</div>
</div>
<!-- Fixed elements (NOT affected by zoom) -->
<footer>...</footer>
<button id="back-to-top">...</button>
<button id="info-button">...</button>
<div id="zoom-control">...</div>
Critical Design Decisions
Decision 1: CSS zoom vs transform: scale()
Choice: CSS zoom property
Why:
- Layout impact:
zoomaffects actual layout dimensions, making scrollbars appear naturally - Footer positioning: With
zoom, footer automatically follows zoomed content - Browser behavior: Mimics native browser zoom behavior more closely
Why NOT transform: scale():
- Only visual transformation, doesn't affect layout
- Creates infinite gray space below content
- Requires complex margin calculations to position footer
- Doesn't trigger natural scrollbar behavior
Decision 2: Inverse Zoom for Fixed Elements
Problem: CSS zoom on parent can affect fixed-position children in some rendering contexts
Solution: Apply inverse zoom to elements that must stay constant size
const inverseZoom = 1 / zoomLevel;
button.style.zoom = inverseZoom;
Example:
- Content zoom: 150% (1.5)
- Button inverse zoom: 66.67% (1/1.5)
- Result: Button appears at 100% (1.5 × 0.667 = 1.0)
Affected Elements:
#back-to-topbutton#info-buttonbutton- Note:
#zoom-controland.action-barare naturally outside zoom context
Decision 3: Viewport Expansion Strategy
Problem: Elements with width: 100% stay constrained to viewport even when zoomed
Solution: Set min-width on zoom wrapper when zoom > 100%
if (zoomLevel > 1) {
zoomWrapper.style.minWidth = `${window.innerWidth}px`;
}
Effect:
- Content can expand beyond viewport width
- Horizontal scrollbar appears automatically
- User can scroll to see all zoomed content
CSS Support:
body {
overflow-x: auto; /* Enable horizontal scroll */
}
Constraints and Limitations
1. Browser Compatibility
CSS zoom property:
- ✅ Chrome, Edge, Safari: Full support
- ⚠️ Firefox: Supported since Firefox 126 (May 2024)
- Fallback: Content remains at 100% zoom in unsupported browsers
2. Range Limitations
Current Range: 25% - 175%
Why centered at 100%:
- Slider midpoint = (25 + 175) / 2 = 100
- Users expect "reset" to be at slider center
- Provides balanced zoom-in/zoom-out range
Why NOT 50% - 200%:
- 50% minimum would make slider center = 125%, confusing UX
- Current range provides adequate zoom range while maintaining intuitive controls
3. Fixed Element Positioning
Elements that MUST stay outside zoom-wrapper:
- Action bar (navigation)
- Zoom control
- Fixed buttons (info, back-to-top)
- Footer
If placed inside zoom-wrapper:
- Would scale with content
- Requires inverse zoom application (added complexity)
- Footer positioning becomes unreliable
4. Performance Considerations
CSS zoom Performance:
- Generally performant (GPU-accelerated in modern browsers)
- Can cause reflow when changed
- Mitigated by
requestAnimationFrame()wrapper
Optimization:
requestAnimationFrame(() => {
zoomWrapper.style.zoom = zoomLevel;
// Other DOM modifications
});
5. Mobile Behavior
Current Implementation: Zoom control hidden on mobile (≤768px viewport)
Rationale:
- Mobile browsers provide native pinch-to-zoom
- Custom zoom control redundant on touch devices
- Saves screen space on small viewports
Code:
function isMobileView() {
return window.innerWidth <= 768;
}
Edge Cases Handled
1. Zoom Persistence
Behavior: Zoom level saved to localStorage
localStorage.setItem('cv-zoom', zoomValue.toString());
On Page Load: Restore saved zoom level
const savedZoom = localStorage.getItem('cv-zoom');
if (savedZoom) {
applyZoom(parseInt(savedZoom, 10), false);
}
2. Responsive Resize
Problem: User resizes window while zoomed
Solution: Recalculate min-width on window resize
window.addEventListener('resize', function() {
// Debounced resize handler
if (isMobileView()) {
applyZoom(100, false); // Reset on mobile
}
});
3. Reset Button Green Indicator
Feature: Reset button turns green when zoom ≠ 100%
Implementation:
if (zoomValue !== 100) {
resetBtn.classList.add('zoom-not-default');
} else {
resetBtn.classList.remove('zoom-not-default');
}
CSS:
.zoom-reset-btn.zoom-not-default:hover {
background: #74aacd;
}
4. Keyboard Shortcuts
Supported Shortcuts:
Ctrl/Cmd + Plus: Zoom in (+10%)Ctrl/Cmd + Minus: Zoom out (-10%)Ctrl/Cmd + 0: Reset to 100%
Implementation:
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && !e.shiftKey) {
if (e.key === '=' || e.key === '+') {
e.preventDefault();
incrementZoom(10);
}
// ... other shortcuts
}
});
Bottom Scroll Detection Integration
Feature: Info and back-to-top buttons turn green when at page bottom
Challenge: Zoom affects scroll calculations
Solution: Bottom detection works independently of zoom
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
const isAtBottom = (scrollHeight - currentScroll - clientHeight) < 50;
Why it works: scrollHeight and clientHeight automatically account for zoomed content dimensions
Future Considerations
Potential Enhancements
-
Smooth Zoom Transitions
- Current: Instant zoom change
- Possible: CSS transition on zoom property
- Trade-off: May feel laggy for large zoom changes
-
Zoom Presets
- Current: Free-range slider
- Possible: Preset buttons (50%, 75%, 100%, 125%, 150%)
- Trade-off: Less flexible, but faster access
-
Per-Section Zoom
- Current: Entire document zooms
- Possible: Zoom specific sections independently
- Trade-off: Complex state management
Known Limitations to Monitor
- Firefox < 126: No CSS zoom support (falls back to 100%)
- Print Behavior: Zoom is reset for print (intentional)
- High Zoom Levels: Text rendering quality may degrade > 175%
Testing Checklist
- Zoom in/out with slider
- Zoom with keyboard shortcuts
- Reset button functionality
- Fixed elements stay constant size
- Horizontal scroll appears when needed
- Bottom detection works with zoom
- Green button indicators work correctly
- localStorage persistence works
- Mobile: Zoom control hidden
- Print: Zoom resets properly
References
- CSS zoom specification: MDN Web Docs
- Browser compatibility: Can I Use - CSS zoom
- Related: transform scale vs zoom discussion
Last Updated: 2025-11-12 Author: Development Team Status: Implemented and Stable