Python Full Stack Web Developer Course

Week 4: For JavaScript Developers - Advanced Responsive Patterns

Introduction to Advanced Responsive Patterns

While CSS media queries form the foundation of responsive design, JavaScript developers can leverage advanced patterns to create truly adaptive experiences that go beyond simple layout changes. This session explores sophisticated responsive techniques that enhance user experience across all devices.

As JavaScript developers transitioning to Python full-stack development, you already have a valuable perspective on client-side interactivity. We'll build on that knowledge to create responsive systems that intelligently adapt both presentation and behavior.

File Location: Create a new file in your project at 04week/04week_3day_js_advanced_responsive.html and corresponding JavaScript at 04week/js/advanced_responsive.js

Beyond Media Queries: JavaScript-Enhanced Responsive Design

While CSS media queries excel at layout adjustments, JavaScript enables more nuanced responsiveness based on device capabilities, user preferences, and interaction patterns.

The Responsive Enhancement Pyramid

Think of responsive design as a pyramid with three layers:

  1. Foundation (CSS): Basic layout adaptation using media queries
  2. Capability Enhancement (JS): Feature adjustments based on device capabilities
  3. Behavioral Adaptation (JS): Interaction models tailored to device context

As JavaScript developers, you're uniquely positioned to implement the upper layers of this pyramid, creating truly context-aware applications that transcend simple layout adjustments.

Feature Detection and Capability-Based Responsive Design

Unlike CSS media queries which primarily detect viewport dimensions, JavaScript can detect precise device capabilities and adapt accordingly.

Using Feature Detection Libraries

Modernizr remains the gold standard for feature detection, though modern browsers offer native methods for many capabilities:

// Check for touch capability
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;

// Check for specific CSS features
const supportsGridLayout = CSS.supports('display', 'grid');
const supportsCustomProperties = CSS.supports('--custom-property', 'value');

// Check for advanced API availability
const hasIntersectionObserver = 'IntersectionObserver' in window;
const hasGeolocation = 'geolocation' in navigator;

// Apply different behaviors based on capabilities
if (hasTouch) {
    initializeTouchFriendlyInteractions();
} else {
    initializeHoverInteractions();
}

// Load different scripts based on capabilities
if (hasIntersectionObserver) {
    import('./advanced-lazy-loading.js').then(module => {
        module.initialize();
    });
} else {
    import('./basic-lazy-loading.js').then(module => {
        module.initialize();
    });
}

This approach allows for much finer-grained responsiveness than CSS media queries alone. Instead of assuming capabilities based on screen size, you're directly checking for the features that matter to your application.

Integration with Python Web Applications

In a Flask or Django application, you can pass detected features back to the server to adapt server responses:

// Client-side detection script
function detectCapabilities() {
    const capabilities = {
        touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
        grid: CSS.supports('display', 'grid'),
        webpSupport: detectWebPSupport(),
        connectionType: navigator.connection ? navigator.connection.effectiveType : 'unknown',
        memory: navigator.deviceMemory || 'unknown'
    };
    
    // Store in sessionStorage for use across page loads
    sessionStorage.setItem('deviceCapabilities', JSON.stringify(capabilities));
    
    // Send to server on first detection
    fetch('/api/capabilities', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(capabilities)
    });
}

// Function to detect WebP support
function detectWebPSupport() {
    const canvas = document.createElement('canvas');
    if (canvas.getContext && canvas.getContext('2d')) {
        return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
    }
    return false;
}

On the Python side, you can use this information to customize responses:

# Flask route for capability detection
@app.route('/api/capabilities', methods=['POST'])
def store_capabilities():
    capabilities = request.json
    session['device_capabilities'] = capabilities
    return jsonify({'status': 'success'})

# Using capabilities to customize responses
@app.route('/images/hero')
def serve_hero_image():
    capabilities = session.get('device_capabilities', {})
    
    # Select image format based on support
    if capabilities.get('webpSupport'):
        image_path = 'static/images/hero.webp'
    else:
        image_path = 'static/images/hero.jpg'
        
    # Adjust quality based on connection
    if capabilities.get('connectionType') == 'slow-2g' or capabilities.get('connectionType') == '2g':
        # Serve lower quality image
        image_path = image_path.replace('hero', 'hero-low')
        
    return send_file(image_path)

This server-client collaboration creates a truly adaptive system that goes far beyond what CSS media queries alone can achieve.

Component-Level Responsive Patterns

Modern web development emphasizes component-based architecture. JavaScript developers can create intelligent components that adapt to their container context, not just the viewport.

Container Queries and JavaScript Alternatives

While container queries are becoming available in CSS, JavaScript has long enabled components to respond to their immediate context:

class ResponsiveComponent extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = `
            
            
`; // Set up the resize observer this.observer = new ResizeObserver(entries => this.handleResize(entries[0])); this.observer.observe(this); } connectedCallback() { // Initial layout adjustment setTimeout(() => this.adjustLayout(), 0); } disconnectedCallback() { this.observer.disconnect(); } handleResize(entry) { const width = entry.contentRect.width; this.adjustLayout(width); } adjustLayout(width = this.offsetWidth) { // Remove any existing layout classes this.classList.remove('layout-small', 'layout-medium', 'layout-large'); // Add appropriate layout class if (width < 300) { this.classList.add('layout-small'); } else if (width < 600) { this.classList.add('layout-medium'); } else { this.classList.add('layout-large'); } } } customElements.define('responsive-component', ResponsiveComponent);

This pattern creates truly encapsulated components that adapt to their container, not just the viewport. This is especially useful when:

Implementation in Python Web Applications

In a Flask or Django application, you can create template components that follow this pattern:


{% macro product_card(product, container_class="") %}
{{ product.name }}

{{ product.name }}

{{ product.price }}

{% endmacro %}

This approach allows components to adapt regardless of where they're used in your application, creating a more flexible system than viewport-based media queries alone.

Responsive Animation Patterns

As JavaScript developers, you know that animations aren't just decorative—they provide crucial feedback and guide users through interactions. Responsive animations adapt not just to screen size but to user preferences and device capabilities.

Motion Reduction and User Preferences

Modern browsers support the prefers-reduced-motion media query, which you can detect with JavaScript:

// Check if user prefers reduced motion
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

// Animation factory that respects user preferences
function createAnimation(element, properties, options = {}) {
    // Default options
    const defaults = {
        duration: 300,
        easing: 'ease-in-out',
        reduceMotion: true // Whether this animation should respect reduced motion
    };
    
    const settings = { ...defaults, ...options };
    
    // If user prefers reduced motion and this animation should respect that
    if (prefersReducedMotion && settings.reduceMotion) {
        // Either skip animation entirely
        element.style = properties.target;
        return Promise.resolve();
        
        // Or use minimal animation
        // return element.animate(properties, { duration: 50 }).finished;
    }
    
    // Otherwise, run full animation
    return element.animate(properties, settings).finished;
}

// Example usage
const menuButton = document.querySelector('.menu-button');
menuButton.addEventListener('click', () => {
    const menu = document.querySelector('.menu');
    
    // Define both complex and simple animations
    const fullAnimation = [
        { transform: 'translateX(-100%)', opacity: 0 },
        { transform: 'translateX(0)', opacity: 1 }
    ];
    
    // Use our factory function
    createAnimation(menu, fullAnimation, { 
        duration: 500,
        easing: 'cubic-bezier(0.17, 0.67, 0.83, 0.67)'
    }).then(() => {
        menu.classList.add('active');
    });
});

Performance-Aware Animations

Responsive animations should adapt to device performance capabilities:

// Performance-aware animation system
class PerformanceAwareAnimator {
    constructor() {
        this.performanceLevel = this.detectPerformance();
        
        // Listen for performance changes (battery, throttling)
        if ('connection' in navigator) {
            navigator.connection.addEventListener('change', () => {
                this.performanceLevel = this.detectPerformance();
            });
        }
    }
    
    detectPerformance() {
        let score = 0;
        
        // Check for low-end device
        if (navigator.deviceMemory && navigator.deviceMemory < 4) {
            score -= 1;
        }
        
        // Check for slow connection
        if (navigator.connection) {
            const connection = navigator.connection;
            if (connection.effectiveType === 'slow-2g' || connection.effectiveType === '2g') {
                score -= 1;
            }
            if (connection.saveData) {
                score -= 1;
            }
        }
        
        // Check for battery status
        if (navigator.getBattery) {
            navigator.getBattery().then(battery => {
                if (battery.level < 0.15 && !battery.charging) {
                    score -= 1;
                }
            });
        }
        
        // Check for page visibility (reduce animations in background tabs)
        if (document.hidden) {
            score -= 1;
        }
        
        // Classify performance level
        if (score < -2) return 'minimal';
        if (score < 0) return 'reduced';
        return 'full';
    }
    
    animate(element, options) {
        const { minimal, reduced, full } = options;
        
        switch(this.performanceLevel) {
            case 'minimal':
                return minimal();
            case 'reduced':
                return reduced();
            case 'full':
            default:
                return full();
        }
    }
}

// Usage example
const animator = new PerformanceAwareAnimator();

function openModal(modal) {
    animator.animate(modal, {
        // No animation, just show
        minimal: () => {
            modal.style.display = 'block';
            return Promise.resolve();
        },
        
        // Simple fade
        reduced: () => {
            modal.style.display = 'block';
            return modal.animate([
                { opacity: 0 },
                { opacity: 1 }
            ], { duration: 200 }).finished;
        },
        
        // Full entrance animation
        full: () => {
            modal.style.display = 'block';
            return modal.animate([
                { transform: 'scale(0.8)', opacity: 0 },
                { transform: 'scale(1)', opacity: 1 }
            ], {
                duration: 350,
                easing: 'cubic-bezier(0.17, 0.67, 0.83, 0.67)'
            }).finished;
        }
    });
}

This pattern ensures that animations adapt to the device's performance capabilities, providing the best experience possible without negatively impacting battery life or performance.

Content Adaptation Patterns

Truly responsive applications adapt not just their presentation but their content to different contexts. JavaScript developers can implement sophisticated content adaptation strategies.

Progressive Loading

Load increasing levels of content detail as the device/connection allows:

class ProgressiveContentLoader {
    constructor(options = {}) {
        this.options = {
            initialContentSelector: '.content-essential',
            enhancedContentSelector: '.content-enhanced',
            fullContentSelector: '.content-full',
            ...options
        };
        
        this.connectionType = this.getConnectionType();
        this.initialized = false;
    }
    
    getConnectionType() {
        if (!navigator.connection) return 'unknown';
        return navigator.connection.effectiveType || 'unknown';
    }
    
    init() {
        if (this.initialized) return;
        this.initialized = true;
        
        // Always load initial content
        this.loadInitialContent();
        
        // Load enhanced content based on connection
        if (this.connectionType !== 'slow-2g' && this.connectionType !== '2g') {
            this.loadEnhancedContent();
        }
        
        // Load full content for fast connections
        if (this.connectionType === '4g') {
            this.loadFullContent();
        } else {
            // For slower connections, add buttons to load more content
            this.addLoadMoreButtons();
        }
    }
    
    loadInitialContent() {
        // Initial content is loaded with the page
        document.querySelectorAll(this.options.initialContentSelector).forEach(el => {
            el.style.display = 'block';
        });
    }
    
    loadEnhancedContent() {
        document.querySelectorAll(this.options.enhancedContentSelector).forEach(el => {
            // If data-src attribute exists, it's likely an image or iframe to load
            if (el.dataset.src) {
                if (el.tagName === 'IMG' || el.tagName === 'IFRAME') {
                    el.src = el.dataset.src;
                } else {
                    el.style.backgroundImage = `url(${el.dataset.src})`;
                }
            }
            
            // Load content via AJAX if needed
            if (el.dataset.contentUrl) {
                fetch(el.dataset.contentUrl)
                    .then(response => response.text())
                    .then(html => {
                        el.innerHTML = html;
                    });
            }
            
            el.style.display = 'block';
        });
    }
    
    loadFullContent() {
        document.querySelectorAll(this.options.fullContentSelector).forEach(el => {
            if (el.dataset.src) {
                if (el.tagName === 'IMG' || el.tagName === 'IFRAME') {
                    el.src = el.dataset.src;
                } else {
                    el.style.backgroundImage = `url(${el.dataset.src})`;
                }
            }
            
            if (el.dataset.contentUrl) {
                fetch(el.dataset.contentUrl)
                    .then(response => response.text())
                    .then(html => {
                        el.innerHTML = html;
                    });
            }
            
            el.style.display = 'block';
        });
    }
    
    addLoadMoreButtons() {
        // Add "Load more content" buttons for enhanced content
        const enhancedElements = document.querySelectorAll(this.options.enhancedContentSelector);
        if (enhancedElements.length > 0) {
            const loadMoreButton = document.createElement('button');
            loadMoreButton.className = 'load-more-button';
            loadMoreButton.textContent = 'Load enhanced content';
            loadMoreButton.addEventListener('click', () => {
                this.loadEnhancedContent();
                loadMoreButton.remove();
                
                // Show load full content button if applicable
                if (document.querySelectorAll(this.options.fullContentSelector).length > 0) {
                    document.querySelector('.load-full-button').style.display = 'block';
                }
            });
            
            document.body.appendChild(loadMoreButton);
        }
        
        // Add "Load full experience" button
        const fullElements = document.querySelectorAll(this.options.fullContentSelector);
        if (fullElements.length > 0) {
            const loadFullButton = document.createElement('button');
            loadFullButton.className = 'load-full-button';
            loadFullButton.textContent = 'Load full experience';
            loadFullButton.style.display = enhancedElements.length > 0 ? 'none' : 'block';
            loadFullButton.addEventListener('click', () => {
                this.loadFullContent();
                loadFullButton.remove();
            });
            
            document.body.appendChild(loadFullButton);
        }
    }
}

// Usage
document.addEventListener('DOMContentLoaded', () => {
    const loader = new ProgressiveContentLoader();
    loader.init();
});

Implementing in Python Web Applications

This pattern complements server-side techniques in Flask or Django applications:

# Flask route with progressive loading support
@app.route('/article/')
def article(slug):
    article = Article.query.filter_by(slug=slug).first_or_404()
    
    # Check if this is an AJAX request for enhanced content
    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        content_type = request.args.get('content_type', 'enhanced')
        
        if content_type == 'enhanced':
            return render_template('_article_enhanced_content.html', article=article)
        elif content_type == 'full':
            return render_template('_article_full_content.html', article=article)
    
    # Initial page load includes basic content
    return render_template('article.html', article=article)

# Corresponding Jinja2 template (article.html)
{% extends "base.html" %}

{% block content %}

{{ article.title }}

{{ article.published_date }}

{{ article.summary | safe }}
{% endblock %}

This collaborative approach between frontend and backend creates truly adaptive experiences that respond to both device capabilities and network conditions.

Responsive Interaction Patterns

Beyond layout and content, truly responsive applications adapt their interaction models to different input methods.

Input Method Detection

Create interfaces that automatically adapt to touch, pointer, keyboard, or voice input:

class InputMethodDetector {
    constructor() {
        this.currentInputMethod = 'unknown';
        this.previousInputMethod = 'unknown';
        this.listeners = [];
        
        // Setup detection events
        this.setupDetection();
    }
    
    setupDetection() {
        // Detect mouse
        document.addEventListener('mousemove', this.handleMouseMove.bind(this), { passive: true });
        
        // Detect touch
        document.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true });
        
        // Detect keyboard
        document.addEventListener('keydown', this.handleKeyDown.bind(this));
        
        // Detect speech (if available)
        if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
            this.setupSpeechDetection();
        }
        
        // Check for initial touch capability
        if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
            this.setInputMethod('touch');
        } else {
            this.setInputMethod('mouse');
        }
    }
    
    handleMouseMove(event) {
        // Ignore simulated mouse events that follow touch
        if (event.sourceCapabilities && event.sourceCapabilities.firesTouchEvents) {
            return;
        }
        this.setInputMethod('mouse');
    }
    
    handleTouchStart() {
        this.setInputMethod('touch');
    }
    
    handleKeyDown(event) {
        // Ignore modifier keys and keys that are likely part of mouse/touch interactions
        const ignoredKeys = ['Shift', 'Control', 'Alt', 'Meta', 'Enter', 'Escape'];
        if (!ignoredKeys.includes(event.key)) {
            this.setInputMethod('keyboard');
        }
    }
    
    setupSpeechDetection() {
        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
        this.recognition = new SpeechRecognition();
        this.recognition.continuous = true;
        this.recognition.interimResults = true;
        
        this.recognition.onstart = () => {
            this.setInputMethod('voice');
        };
        
        // Listen for speech input command
        document.addEventListener('keydown', (event) => {
            if (event.key === '/' && event.ctrlKey) {
                this.recognition.start();
            }
        });
    }
    
    setInputMethod(method) {
        if (this.currentInputMethod !== method) {
            this.previousInputMethod = this.currentInputMethod;
            this.currentInputMethod = method;
            
            // Update document data attribute for CSS targeting
            document.documentElement.setAttribute('data-input-method', method);
            
            // Trigger listeners
            this.notify();
        }
    }
    
    onInputMethodChange(callback) {
        this.listeners.push(callback);
        
        // Immediately call with current state
        callback(this.currentInputMethod, this.previousInputMethod);
        
        // Return unsubscribe function
        return () => {
            this.listeners = this.listeners.filter(listener => listener !== callback);
        };
    }
    
    notify() {
        this.listeners.forEach(callback => {
            callback(this.currentInputMethod, this.previousInputMethod);
        });
    }
}

// Usage
const inputDetector = new InputMethodDetector();

inputDetector.onInputMethodChange((current, previous) => {
    console.log(`Input method changed from ${previous} to ${current}`);
    
    // Adjust UI based on input method
    if (current === 'touch') {
        // Increase target sizes, spacing
        document.body.classList.add('touch-input');
        document.body.classList.remove('mouse-input', 'keyboard-input', 'voice-input');
    } else if (current === 'mouse') {
        // Enable hover effects, smaller targets
        document.body.classList.add('mouse-input');
        document.body.classList.remove('touch-input', 'keyboard-input', 'voice-input');
    } else if (current === 'keyboard') {
        // Enhance focus styles, show shortcuts
        document.body.classList.add('keyboard-input');
        document.body.classList.remove('touch-input', 'mouse-input', 'voice-input');
    } else if (current === 'voice') {
        // Show voice feedback UI
        document.body.classList.add('voice-input');
        document.body.classList.remove('touch-input', 'mouse-input', 'keyboard-input');
    }
});

Adaptive Controls

Create UI controls that transform based on input method:

class AdaptiveControl extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        
        // Base template
        this.shadowRoot.innerHTML = `
            
            
`; // Current input method this.inputMethod = document.documentElement.getAttribute('data-input-method') || 'mouse'; // Control types defined in attributes this.mouseVersion = this.getAttribute('mouse-control') || 'default'; this.touchVersion = this.getAttribute('touch-control') || 'default'; this.keyboardVersion = this.getAttribute('keyboard-control') || 'default'; this.voiceVersion = this.getAttribute('voice-control') || 'default'; } connectedCallback() { // Listen for input method changes this.observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.attributeName === 'data-input-method') { const newInputMethod = document.documentElement.getAttribute('data-input-method'); this.updateControl(newInputMethod); } }); }); this.observer.observe(document.documentElement, { attributes: true }); // Initial setup this.updateControl(this.inputMethod); } disconnectedCallback() { this.observer.disconnect(); } updateControl(inputMethod) { this.inputMethod = inputMethod; // Remove current control type classes this.classList.remove('mouse-variant', 'touch-variant', 'keyboard-variant', 'voice-variant'); // Add new control type class this.classList.add(`${inputMethod}-variant`); // Adjust internal structure based on input method switch(inputMethod) { case 'touch': this.updateTouchVersion(); break; case 'mouse': this.updateMouseVersion(); break; case 'keyboard': this.updateKeyboardVersion(); break; case 'voice': this.updateVoiceVersion(); break; } } updateTouchVersion() { const control = this.shadowRoot.querySelector('.control'); // Apply touch-specific styles control.style.padding = '16px'; control.style.fontSize = '1.2em'; // Load touch-specific template if defined if (this.touchVersion !== 'default') { // This could fetch a template or apply different structure fetch(`/templates/controls/${this.touchVersion}.html`) .then(response => response.text()) .then(html => { control.innerHTML = html; }); } } updateMouseVersion() { const control = this.shadowRoot.querySelector('.control'); // Reset to default styles control.style.padding = '1rem'; control.style.fontSize = '1em'; // Load mouse-specific template if defined if (this.mouseVersion !== 'default') { fetch(`/templates/controls/${this.mouseVersion}.html`) .then(response => response.text()) .then(html => { control.innerHTML = html; }); } } updateKeyboardVersion() { const control = this.shadowRoot.querySelector('.control'); // Add keyboard focus styles and shortcuts control.style.outline = '2px solid blue'; // Load keyboard-specific template if defined if (this.keyboardVersion !== 'default') { fetch(`/templates/controls/${this.keyboardVersion}.html`) .then(response => response.text()) .then(html => { control.innerHTML = html; }); } } updateVoiceVersion() { const control = this.shadowRoot.querySelector('.control'); // Add voice interaction indicators control.style.border = '2px solid purple'; // Load voice-specific template if defined if (this.voiceVersion !== 'default') { fetch(`/templates/controls/${this.voiceVersion}.html`) .then(response => response.text()) .then(html => { control.innerHTML = html; }); } } } customElements.define('adaptive-control', AdaptiveControl);

Usage in HTML

Use these components in your HTML:

<!-- Basic usage -->
<adaptive-control>
    <button>Click me</button>
</adaptive-control>

<!-- With specific control variants -->
<adaptive-control 
    mouse-control="slider" 
    touch-control="large-buttons"
    keyboard-control="shortcuts"
    voice-control="voice-commands">
    Volume Control
</adaptive-control>

This pattern creates truly adaptive interfaces that respond to how users are interacting with your application at any given moment.

Integration with Python Web Frameworks

As a JavaScript developer transitioning to Python full-stack development, you can apply these patterns within Flask and Django applications.

Client-Server Collaboration

Create a system where client-side detection informs server responses:

# In Flask application
@app.route('/api/device-context', methods=['POST'])
def store_device_context():
    context = request.json
    session['device_context'] = context
    return jsonify({'status': 'success'})

# Context-aware template rendering
@app.route('/dashboard')
def dashboard():
    device_context = session.get('device_context', {})
    
    # Default values if no context detected
    context = {
        'input_method': 'mouse',
        'screen_size': 'desktop',
        'performance_level': 'high',
        'connection_type': '4g'
    }
    
    # Update with detected values if available
    if device_context:
        context.update(device_context)
    
    # Select appropriate template version
    if context['screen_size'] == 'mobile' and context['performance_level'] == 'low':
        template = 'dashboard_minimal.html'
    elif context['input_method'] == 'touch':
        template = 'dashboard_touch.html'
    else:
        template = 'dashboard.html'
    
    return render_template(template, context=context)

JavaScript Client-Side Code

Detect and send context information to the server:

// device-context.js
class DeviceContextDetector {
    constructor() {
        this.context = {
            screen_size: this.detectScreenSize(),
            input_method: this.detectInputMethod(),
            performance_level: this.detectPerformance(),
            connection_type: this.detectConnection(),
            preferences: this.detectPreferences()
        };
        
        // Update context when things change
        this.setupListeners();
        
        // Send initial context to server
        this.sendToServer();
    }
    
    detectScreenSize() {
        const width = window.innerWidth;
        if (width < 576) return 'mobile';
        if (width < 992) return 'tablet';
        return 'desktop';
    }
    
    detectInputMethod() {
        if ('ontouchstart' in window) return 'touch';
        return 'mouse';
    }
    
    detectPerformance() {
        // Simple heuristic based on device memory
        if (navigator.deviceMemory && navigator.deviceMemory < 4) {
            return 'low';
        }
        return 'high';
    }
    
    detectConnection() {
        if (navigator.connection) {
            return navigator.connection.effectiveType;
        }
        return 'unknown';
    }
    
    detectPreferences() {
        return {
            reduced_motion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
            color_scheme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',
            contrast: window.matchMedia('(prefers-contrast: more)').matches ? 'high' : 'normal'
        };
    }
    
    setupListeners() {
        // Listen for resize events
        window.addEventListener('resize', this.handleResize.bind(this));
        
        // Listen for connection changes
        if (navigator.connection) {
            navigator.connection.addEventListener('change', this.handleConnectionChange.bind(this));
        }
        
        // Listen for preference changes
        window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', this.handlePreferenceChange.bind(this));
        window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', this.handlePreferenceChange.bind(this));
    }
    
    handleResize() {
        this.context.screen_size = this.detectScreenSize();
        this.sendToServer();
    }
    
    handleConnectionChange() {
        this.context.connection_type = this.detectConnection();
        this.sendToServer();
    }
    
    handlePreferenceChange() {
        this.context.preferences = this.detectPreferences();
        this.sendToServer();
    }
    
    sendToServer() {
        fetch('/api/device-context', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(this.context)
        });
    }
}

// Initialize when the page loads
document.addEventListener('DOMContentLoaded', () => {
    window.deviceContext = new DeviceContextDetector();
});

This collaborative approach allows your Python backend to make intelligent decisions about what content to serve, while your JavaScript frontend adapts the presentation and interaction model based on device context.

Practice Exercises

Apply these advanced responsive patterns with these exercises:

Exercise 1: Create an Adaptive Navigation Component

  1. Build a navigation component that transforms between:
    • A traditional horizontal menu on desktop/mouse input
    • A hamburger menu with large touch targets on mobile/touch input
    • A keyboard-navigable menu with visible shortcut indicators for keyboard input
  2. Use feature detection to adapt the behavior based on device capabilities
  3. Integrate it with a Flask or Django application

Exercise 2: Build a Progressive Loading Dashboard

  1. Create a data dashboard with three levels of content:
    • Essential: Key metrics and simple charts
    • Enhanced: Additional visualizations and filters
    • Full: Detailed data tables and interactive features
  2. Implement progressive loading based on connection speed
  3. Integrate with a Python backend that provides the appropriate data endpoints

Exercise 3: Implement Performance-Aware Animations

  1. Create a set of UI components with animations that adapt based on:
    • Device performance capabilities
    • User motion preferences
    • Battery status (if available)
  2. Test on different devices to verify adaptation
  3. Document the performance differences

Conclusion: From JavaScript to Python Full Stack

As JavaScript developers learning Python full-stack development, you bring valuable expertise in creating dynamic, responsive user interfaces. The patterns explored in this session go beyond basic responsive design techniques to create truly adaptive applications.

These advanced patterns are particularly valuable in Python web applications, where the collaboration between backend server logic and frontend adaptation creates experiences optimized for each user's context. Whether you're working with Flask, Django, or other Python frameworks, these patterns help create applications that:

The transition from JavaScript to Python full-stack development is an opportunity to create truly holistic responsive experiences that leverage the strengths of both client and server technologies. By combining these advanced responsive patterns with Python's powerful backend capabilities, you'll build applications that provide optimal experiences across the entire spectrum of devices and contexts.

Next Steps: In upcoming sessions, we'll continue building on these foundations as we explore how to integrate these responsive patterns with Python data processing and API structures.

Further Resources