docs: add comprehensive zoom implementation technical documentation
- Document CSS zoom vs transform scale decision - Explain inverse zoom technique for fixed elements - Detail viewport expansion strategy - Cover edge cases, constraints, and browser compatibility - Include testing checklist and future considerations
This commit is contained in:
@@ -0,0 +1,332 @@
|
|||||||
|
# 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 `zoom` property 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
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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`
|
||||||
|
|
||||||
|
```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**: `zoom` affects 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
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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-top` button
|
||||||
|
- `#info-button` button
|
||||||
|
- Note: `#zoom-control` and `.action-bar` are 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%
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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**:
|
||||||
|
```css
|
||||||
|
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**:
|
||||||
|
```javascript
|
||||||
|
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**:
|
||||||
|
```javascript
|
||||||
|
function isMobileView() {
|
||||||
|
return window.innerWidth <= 768;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Edge Cases Handled
|
||||||
|
|
||||||
|
### 1. Zoom Persistence
|
||||||
|
|
||||||
|
**Behavior**: Zoom level saved to `localStorage`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
localStorage.setItem('cv-zoom', zoomValue.toString());
|
||||||
|
```
|
||||||
|
|
||||||
|
**On Page Load**: Restore saved zoom level
|
||||||
|
```javascript
|
||||||
|
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
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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**:
|
||||||
|
```javascript
|
||||||
|
if (zoomValue !== 100) {
|
||||||
|
resetBtn.classList.add('zoom-not-default');
|
||||||
|
} else {
|
||||||
|
resetBtn.classList.remove('zoom-not-default');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**CSS**:
|
||||||
|
```css
|
||||||
|
.zoom-reset-btn.zoom-not-default:hover {
|
||||||
|
background: rgba(39, 174, 96, 0.5); /* Green */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Keyboard Shortcuts
|
||||||
|
|
||||||
|
**Supported Shortcuts**:
|
||||||
|
- `Ctrl/Cmd + Plus`: Zoom in (+10%)
|
||||||
|
- `Ctrl/Cmd + Minus`: Zoom out (-10%)
|
||||||
|
- `Ctrl/Cmd + 0`: Reset to 100%
|
||||||
|
|
||||||
|
**Implementation**:
|
||||||
|
```javascript
|
||||||
|
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
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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
|
||||||
|
|
||||||
|
1. **Smooth Zoom Transitions**
|
||||||
|
- Current: Instant zoom change
|
||||||
|
- Possible: CSS transition on zoom property
|
||||||
|
- Trade-off: May feel laggy for large zoom changes
|
||||||
|
|
||||||
|
2. **Zoom Presets**
|
||||||
|
- Current: Free-range slider
|
||||||
|
- Possible: Preset buttons (50%, 75%, 100%, 125%, 150%)
|
||||||
|
- Trade-off: Less flexible, but faster access
|
||||||
|
|
||||||
|
3. **Per-Section Zoom**
|
||||||
|
- Current: Entire document zooms
|
||||||
|
- Possible: Zoom specific sections independently
|
||||||
|
- Trade-off: Complex state management
|
||||||
|
|
||||||
|
### Known Limitations to Monitor
|
||||||
|
|
||||||
|
1. **Firefox < 126**: No CSS zoom support (falls back to 100%)
|
||||||
|
2. **Print Behavior**: Zoom is reset for print (intentional)
|
||||||
|
3. **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](https://developer.mozilla.org/en-US/docs/Web/CSS/zoom)
|
||||||
|
- Browser compatibility: [Can I Use - CSS zoom](https://caniuse.com/css-zoom)
|
||||||
|
- Related: transform scale vs zoom discussion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated**: 2025-11-12
|
||||||
|
**Author**: Development Team
|
||||||
|
**Status**: Implemented and Stable
|
||||||
Reference in New Issue
Block a user