工程化与框架系列(23)--前端性能优化(下)

前端性能优化(用户体验) 🎨

引言

用户体验(UX)性能优化是前端性能优化的重要组成部分。本文将探讨如何通过优化用户体验相关的性能指标,提升用户对应用的满意度,包括感知性能、交互响应、视觉反馈等关键方面。

用户体验性能概述

用户体验性能优化主要关注以下方面:

  • 感知性能:用户对应用速度的主观感受
  • 交互响应:用户操作的即时反馈
  • 视觉反馈:加载状态和过渡动画
  • 错误处理:优雅的错误提示和恢复
  • 离线体验:网络不稳定时的应用表现

感知性能优化

骨架屏实现

// 骨架屏组件
class SkeletonScreen {
    private container: HTMLElement;
    private template: string;
    
    constructor(container: HTMLElement, template: string) {
        this.container = container;
        this.template = template;
    }
    
    // 显示骨架屏
    show(): void {
        this.container.innerHTML = this.template;
        this.container.querySelectorAll('.skeleton-item').forEach(item => {
            item.classList.add('skeleton-animation');
        });
    }
    
    // 隐藏骨架屏
    hide(): void {
        this.container.querySelectorAll('.skeleton-item').forEach(item => {
            item.classList.remove('skeleton-animation');
        });
    }
    
    // 创建骨架屏样式
    static createStyles(): void {
        const style = document.createElement('style');
        style.textContent = `
            .skeleton-item {
                background: #f0f0f0;
                border-radius: 4px;
            }
            
            .skeleton-animation {
                animation: skeleton-loading 1.5s infinite;
            }
            
            @keyframes skeleton-loading {
                0% {
                    background-position: -200px 0;
                }
                100% {
                    background-position: calc(200px + 100%) 0;
                }
            }
        `;
        document.head.appendChild(style);
    }
}

// 使用示例
const container = document.getElementById('content')!;
const template = `
    <div class="skeleton-item" style="width: 100%; height: 200px;"></div>
    <div class="skeleton-item" style="width: 60%; height: 20px; margin-top: 20px;"></div>
    <div class="skeleton-item" style="width: 80%; height: 20px; margin-top: 10px;"></div>
`;

const skeleton = new SkeletonScreen(container, template);
SkeletonScreen.createStyles();

// 显示骨架屏
skeleton.show();

// 加载完成后隐藏
setTimeout(() => {
    skeleton.hide();
    container.innerHTML = '实际内容';
}, 2000);

进度反馈

// 进度反馈管理器
class ProgressManager {
    private progressBar: HTMLElement;
    private progressText: HTMLElement;
    
    constructor() {
        this.createProgressElements();
    }
    
    // 创建进度条元素
    private createProgressElements(): void {
        this.progressBar = document.createElement('div');
        this.progressBar.className = 'progress-bar';
        
        this.progressText = document.createElement('div');
        this.progressText.className = 'progress-text';
        
        document.body.appendChild(this.progressBar);
        document.body.appendChild(this.progressText);
        
        const style = document.createElement('style');
        style.textContent = `
            .progress-bar {
                position: fixed;
                top: 0;
                left: 0;
                width: 0;
                height: 3px;
                background: #4CAF50;
                transition: width 0.3s ease;
                z-index: 9999;
            }
            
            .progress-text {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: rgba(0, 0, 0, 0.7);
                color: white;
                padding: 10px 20px;
                border-radius: 4px;
                display: none;
                z-index: 9999;
            }
        `;
        document.head.appendChild(style);
    }
    
    // 更新进度
    updateProgress(progress: number, text?: string): void {
        this.progressBar.style.width = `${progress}%`;
        
        if (text) {
            this.progressText.textContent = text;
            this.progressText.style.display = 'block';
        }
        
        if (progress >= 100) {
            setTimeout(() => {
                this.progressBar.style.width = '0';
                this.progressText.style.display = 'none';
            }, 500);
        }
    }
    
    // 模拟进度
    simulateProgress(duration: number = 2000): Promise<void> {
        return new Promise(resolve => {
            let progress = 0;
            const interval = setInterval(() => {
                progress += Math.random() * 10;
                
                if (progress >= 100) {
                    progress = 100;
                    clearInterval(interval);
                    this.updateProgress(progress);
                    resolve();
                } else {
                    this.updateProgress(progress);
                }
            }, duration / 20);
        });
    }
}

// 使用示例
const progress = new ProgressManager();

// 模拟文件上传进度
async function uploadFile(file: File): Promise<void> {
    const total = file.size;
    let loaded = 0;
    
    const reader = new FileReader();
    reader.onprogress = (event) => {
        if (event.lengthComputable) {
            const percentage = (event.loaded / event.total) * 100;
            progress.updateProgress(
                percentage,
                `上传中... ${Math.round(percentage)}%`
            );
        }
    };
    
    reader.onload = () => {
        progress.updateProgress(100, '上传完成!');
    };
    
    reader.readAsArrayBuffer(file);
}

交互响应优化

即时反馈

// 交互反馈管理器
class InteractionFeedback {
    // 点击波纹效果
    static addRippleEffect(element: HTMLElement): void {
        element.style.position = 'relative';
        element.style.overflow = 'hidden';
        
        element.addEventListener('click', (e: MouseEvent) => {
            const rect = element.getBoundingClientRect();
            const ripple = document.createElement('div');
            
            ripple.className = 'ripple';
            ripple.style.position = 'absolute';
            ripple.style.left = `${e.clientX - rect.left}px`;
            ripple.style.top = `${e.clientY - rect.top}px`;
            
            element.appendChild(ripple);
            
            setTimeout(() => ripple.remove(), 1000);
        });
        
        const style = document.createElement('style');
        style.textContent = `
            .ripple {
                width: 20px;
                height: 20px;
                background: rgba(255, 255, 255, 0.7);
                border-radius: 50%;
                transform: scale(0);
                animation: ripple-animation 1s ease-out;
            }
            
            @keyframes ripple-animation {
                to {
                    transform: scale(20);
                    opacity: 0;
                }
            }
        `;
        document.head.appendChild(style);
    }
    
    // 按钮状态管理
    static enhanceButton(
        button: HTMLButtonElement,
        action: () => Promise<void>
    ): void {
        const originalText = button.textContent;
        
        button.addEventListener('click', async () => {
            button.disabled = true;
            button.classList.add('loading');
            
            try {
                await action();
                button.classList.add('success');
                button.textContent = '成功!';
            } catch (error) {
                button.classList.add('error');
                button.textContent = '失败';
            }
            
            setTimeout(() => {
                button.disabled = false;
                button.className = button.className.replace(
                    /(loading|success|error)/g,
                    ''
                );
                button.textContent = originalText;
            }, 2000);
        });
    }
    
    // 表单验证反馈
    static enhanceFormValidation(form: HTMLFormElement): void {
        const inputs = form.querySelectorAll('input, textarea');
        
        inputs.forEach(input => {
            input.addEventListener('input', () => {
                const isValid = input.checkValidity();
                
                if (isValid) {
                    input.classList.remove('invalid');
                    input.classList.add('valid');
                } else {
                    input.classList.remove('valid');
                    input.classList.add('invalid');
                }
            });
        });
    }
}

// 使用示例
const button = document.querySelector('button')!;
InteractionFeedback.addRippleEffect(button);

InteractionFeedback.enhanceButton(
    button as HTMLButtonElement,
    async () => {
        await new Promise(resolve => setTimeout(resolve, 1000));
    }
);

const form = document.querySelector('form')!;
InteractionFeedback.enhanceFormValidation(form);

视觉反馈优化

加载状态管理

// 加载状态管理器
class LoadingManager {
    private static overlay: HTMLElement;
    private static spinner: HTMLElement;
    
    // 初始化加载状态管理器
    static initialize(): void {
        this.createElements();
        this.createStyles();
    }
    
    // 创建加载状态元素
    private static createElements(): void {
        this.overlay = document.createElement('div');
        this.overlay.className = 'loading-overlay';
        
        this.spinner = document.createElement('div');
        this.spinner.className = 'loading-spinner';
        
        this.overlay.appendChild(this.spinner);
        document.body.appendChild(this.overlay);
    }
    
    // 创建样式
    private static createStyles(): void {
        const style = document.createElement('style');
        style.textContent = `
            .loading-overlay {
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(255, 255, 255, 0.8);
                display: none;
                justify-content: center;
                align-items: center;
                z-index: 9999;
            }
            
            .loading-spinner {
                width: 40px;
                height: 40px;
                border: 4px solid #f3f3f3;
                border-top: 4px solid #3498db;
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }
            
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
        `;
        document.head.appendChild(style);
    }
    
    // 显示加载状态
    static show(): void {
        this.overlay.style.display = 'flex';
    }
    
    // 隐藏加载状态
    static hide(): void {
        this.overlay.style.display = 'none';
    }
    
    // 包装异步操作
    static async wrap<T>(
        operation: () => Promise<T>,
        delay: number = 300
    ): Promise<T> {
        const startTime = Date.now();
        this.show();
        
        try {
            const result = await operation();
            const elapsed = Date.now() - startTime;
            
            if (elapsed < delay) {
                await new Promise(resolve => setTimeout(resolve, delay - elapsed));
            }
            
            return result;
        } finally {
            this.hide();
        }
    }
}

// 使用示例
LoadingManager.initialize();

// 包装异步操作
async function fetchData(): Promise<any> {
    return LoadingManager.wrap(async () => {
        const response = await fetch('/api/data');
        return response.json();
    });
}

错误处理优化

错误提示管理

// 错误提示管理器
class ErrorManager {
    private static container: HTMLElement;
    
    // 初始化错误管理器
    static initialize(): void {
        this.createContainer();
        this.createStyles();
        this.setupGlobalErrorHandler();
    }
    
    // 创建错误提示容器
    private static createContainer(): void {
        this.container = document.createElement('div');
        this.container.className = 'error-container';
        document.body.appendChild(this.container);
    }
    
    // 创建样式
    private static createStyles(): void {
        const style = document.createElement('style');
        style.textContent = `
            .error-container {
                position: fixed;
                top: 20px;
                right: 20px;
                z-index: 9999;
            }
            
            .error-message {
                background: #ff5252;
                color: white;
                padding: 15px 20px;
                border-radius: 4px;
                margin-bottom: 10px;
                box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
                animation: slide-in 0.3s ease-out;
            }
            
            @keyframes slide-in {
                from { transform: translateX(100%); }
                to { transform: translateX(0); }
            }
        `;
        document.head.appendChild(style);
    }
    
    // 设置全局错误处理
    private static setupGlobalErrorHandler(): void {
        window.onerror = (message, source, line, column, error) => {
            this.showError(`发生错误: ${message}`);
        };
        
        window.onunhandledrejection = (event) => {
            this.showError(`未处理的Promise错误: ${event.reason}`);
        };
    }
    
    // 显示错误信息
    static showError(message: string, duration: number = 5000): void {
        const errorElement = document.createElement('div');
        errorElement.className = 'error-message';
        errorElement.textContent = message;
        
        this.container.appendChild(errorElement);
        
        setTimeout(() => {
            errorElement.style.animation = 'slide-out 0.3s ease-in forwards';
            setTimeout(() => errorElement.remove(), 300);
        }, duration);
    }
    
    // 处理API错误
    static handleApiError(error: any): void {
        if (error.response) {
            switch (error.response.status) {
                case 400:
                    this.showError('请求参数错误');
                    break;
                case 401:
                    this.showError('未授权,请重新登录');
                    break;
                case 403:
                    this.showError('没有权限访问该资源');
                    break;
                case 404:
                    this.showError('请求的资源不存在');
                    break;
                case 500:
                    this.showError('服务器内部错误');
                    break;
                default:
                    this.showError('发生未知错误');
            }
        } else if (error.request) {
            this.showError('网络请求失败,请检查网络连接');
        } else {
            this.showError(`发生错误: ${error.message}`);
        }
    }
}

// 使用示例
ErrorManager.initialize();

// 显示错误信息
ErrorManager.showError('操作失败,请重试');

// 处理API错误
try {
    await fetch('/api/data');
} catch (error) {
    ErrorManager.handleApiError(error);
}

离线体验优化

Service Worker管理

// Service Worker管理器
class ServiceWorkerManager {
    private static registration: ServiceWorkerRegistration | null = null;
    
    // 注册Service Worker
    static async register(scriptUrl: string): Promise<void> {
        if ('serviceWorker' in navigator) {
            try {
                this.registration = await navigator.serviceWorker.register(scriptUrl);
                console.log('Service Worker注册成功');
                
                this.setupUpdateFlow();
            } catch (error) {
                console.error('Service Worker注册失败:', error);
            }
        }
    }
    
    // 设置更新流程
    private static setupUpdateFlow(): void {
        if (!this.registration) return;
        
        // 检查更新
        this.registration.addEventListener('updatefound', () => {
            const newWorker = this.registration!.installing;
            
            if (newWorker) {
                newWorker.addEventListener('statechange', () => {
                    if (newWorker.state === 'installed' &&
                        navigator.serviceWorker.controller) {
                        this.showUpdateNotification();
                    }
                });
            }
        });
    }
    
    // 显示更新提示
    private static showUpdateNotification(): void {
        const notification = document.createElement('div');
        notification.className = 'update-notification';
        notification.innerHTML = `
            <p>有新版本可用</p>
            <button onclick="location.reload()">立即更新</button>
        `;
        
        document.body.appendChild(notification);
    }
    
    // 预缓存资源
    static async precacheResources(resources: string[]): Promise<void> {
        const cache = await caches.open('app-cache-v1');
        await cache.addAll(resources);
    }
    
    // 清理旧缓存
    static async cleanupOldCaches(): Promise<void> {
        const cacheNames = await caches.keys();
        const currentCaches = ['app-cache-v1'];
        
        for (const cacheName of cacheNames) {
            if (!currentCaches.includes(cacheName)) {
                await caches.delete(cacheName);
            }
        }
    }
}

// Service Worker脚本示例
const serviceWorkerScript = `
    const CACHE_NAME = 'app-cache-v1';
    const OFFLINE_PAGE = '/offline.html';
    
    self.addEventListener('install', (event) => {
        event.waitUntil(
            caches.open(CACHE_NAME)
                .then(cache => cache.add(OFFLINE_PAGE))
        );
    });
    
    self.addEventListener('fetch', (event) => {
        event.respondWith(
            fetch(event.request)
                .catch(() => {
                    return caches.match(event.request)
                        .then(response => {
                            if (response) {
                                return response;
                            }
                            return caches.match(OFFLINE_PAGE);
                        });
                })
        );
    });
    
    self.addEventListener('activate', (event) => {
        event.waitUntil(
            caches.keys()
                .then(cacheNames => {
                    return Promise.all(
                        cacheNames
                            .filter(cacheName => cacheName !== CACHE_NAME)
                            .map(cacheName => caches.delete(cacheName))
                    );
                })
        );
    });
`;

// 使用示例
// 注册Service Worker
ServiceWorkerManager.register('/sw.js');

// 预缓存资源
ServiceWorkerManager.precacheResources([
    '/',
    '/index.html',
    '/styles.css',
    '/app.js',
    '/offline.html'
]);

最佳实践与建议

  1. 感知性能优化

    • 使用骨架屏提供视觉占位
    • 实现渐进式加载
    • 提供明确的进度反馈
    • 优化首屏加载体验
  2. 交互响应优化

    • 提供即时视觉反馈
    • 实现平滑的动画过渡
    • 优化表单交互体验
    • 减少输入延迟
  3. 视觉反馈优化

    • 使用合适的加载指示器
    • 实现优雅的状态转换
    • 提供清晰的操作结果反馈
    • 保持界面的视觉连续性
  4. 错误处理优化

    • 提供友好的错误提示
    • 实现优雅的错误恢复
    • 保持用户数据不丢失
    • 提供问题解决建议
  5. 离线体验优化

    • 实现离线功能支持
    • 优化弱网络下的体验
    • 提供数据同步机制
    • 实现渐进式Web应用

总结

用户体验性能优化是一个持续的过程,需要从用户的角度出发,关注以下几个方面:

  1. 提升感知性能
  2. 优化交互响应
  3. 改进视觉反馈
  4. 完善错误处理
  5. 增强离线体验

通过这些优化策略的综合运用,可以显著提升用户对应用的满意度和使用体验。

学习资源

  1. 用户体验设计指南
  2. 前端性能优化最佳实践
  3. Progressive Web Apps开发指南
  4. 交互设计模式
  5. 离线应用开发策略

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值