效能優化2026年2月23日
Web 效能優化:從載入時間到使用者體驗
分享實際專案中的效能優化經驗,包含圖片優化、程式碼分割和快取策略。
在現代 Web 開發中,效能不僅影響使用者體驗,更直接關係到 SEO 排名和轉換率。本文將分享我在優化 The Lonesome Era 網站過程中的實戰經驗,從 Core Web Vitals 指標到具體的優化技術。
效能優化的重要性
頁面載入時間每增加 1 秒,轉換率就會下降 7%
Google 的研究顯示,效能對使用者行為有直接影響:
- 3 秒規則:超過 3 秒,53% 的使用者會離開
- SEO 影響:Core Web Vitals 是 Google 排名因素
- 轉換率:每 100ms 的改善可提升 1% 轉換率
- 使用者滿意度:快速載入直接影響品牌印象
Core Web Vitals 深度解析
Google 的 Core Web Vitals 包含三個關鍵指標:
1. Largest Contentful Paint (LCP)
目標:< 2.5 秒
LCP 測量頁面主要內容載入完成的時間。優化策略:
<!-- 優化圖片載入 -->
<img src="hero-image.webp"
alt="主要內容圖片"
loading="eager"
fetchpriority="high"
width="800"
height="600">
<!-- 預載入關鍵資源 -->
<link rel="preload" href="hero-image.webp" as="image">
<link rel="preload" href="critical.css" as="style">
2. First Input Delay (FID)
目標:< 100 毫秒
FID 測量使用者首次互動到瀏覽器回應的時間。優化重點:
// 使用 requestIdleCallback 處理非關鍵任務
function performNonCriticalTask() {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
// 執行非關鍵任務
initializeAnalytics();
loadSecondaryContent();
});
} else {
// 降級處理
setTimeout(() => {
initializeAnalytics();
loadSecondaryContent();
}, 1000);
}
}
// 事件委託減少事件監聽器
document.addEventListener('click', (event) => {
if (event.target.matches('.nav-item')) {
handleNavigation(event);
} else if (event.target.matches('.card-link')) {
handleCardClick(event);
}
});
// 防抖處理頻繁事件
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
const handleScroll = debounce(() => {
// 滾動處理邏輯
}, 16); // 約 60fps
3. Cumulative Layout Shift (CLS)
目標:< 0.1
CLS 測量視覺穩定性。避免布局偏移的策略:
/* 為圖片預留空間 */
.image-container {
width: 100%;
aspect-ratio: 16/9;
background-color: #f0f0f0;
position: relative;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
}
/* 為動態內容預留空間 */
.dynamic-content {
min-height: 200px;
position: relative;
}
.loading-placeholder {
width: 100%;
height: 200px;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
圖片優化策略
圖片通常佔網頁總大小的 60-70%,是優化的重點:
現代圖片格式
<!-- 使用 picture 元素提供多種格式 -->
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="描述文字" loading="lazy">
</picture>
<!-- 響應式圖片 -->
<img srcset="small.webp 480w,
medium.webp 768w,
large.webp 1200w"
sizes="(max-width: 480px) 100vw,
(max-width: 768px) 50vw,
33vw"
src="medium.webp"
alt="響應式圖片">
延遲載入實作
// 進階的 Intersection Observer 實作
class LazyImageLoader {
constructor() {
this.imageObserver = null;
this.images = [];
this.init();
}
init() {
if ('IntersectionObserver' in window) {
this.imageObserver = new IntersectionObserver(
this.handleIntersection.bind(this),
{
rootMargin: '50px 0px',
threshold: 0.01
}
);
this.observeImages();
} else {
// 降級處理:直接載入所有圖片
this.loadAllImages();
}
}
observeImages() {
const lazyImages = document.querySelectorAll('img[loading="lazy"]');
lazyImages.forEach(img => {
this.imageObserver.observe(img);
});
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
this.imageObserver.unobserve(img);
}
});
}
loadImage(img) {
return new Promise((resolve, reject) => {
const imageLoader = new Image();
imageLoader.onload = () => {
img.src = imageLoader.src;
img.classList.add('loaded');
resolve(img);
};
imageLoader.onerror = () => {
img.classList.add('error');
reject(new Error(`Failed to load image: ${img.dataset.src}`));
};
imageLoader.src = img.dataset.src || img.src;
});
}
}
// 初始化延遲載入
document.addEventListener('DOMContentLoaded', () => {
new LazyImageLoader();
});
JavaScript 優化技術
程式碼分割與動態載入
// 動態 import 實現程式碼分割
const loadModule = async (moduleName) => {
try {
const module = await import(`./modules/${moduleName}.js`);
return module.default;
} catch (error) {
console.error(`Failed to load module: ${moduleName}`, error);
return null;
}
};
// 路由級別的程式碼分割
const router = {
async navigate(route) {
const routeModule = await loadModule(route);
if (routeModule) {
routeModule.init();
}
}
};
// 功能級別的延遲載入
class FeatureLoader {
static async loadGameEngine() {
if (!this.gameEngine) {
const GameEngine = await import('./game/engine.js');
this.gameEngine = new GameEngine.default();
}
return this.gameEngine;
}
static async loadAnalytics() {
if (navigator.connection && navigator.connection.saveData) {
// 在省流量模式下不載入分析工具
return null;
}
const analytics = await import('./analytics/tracker.js');
return analytics.default;
}
}
// 使用 Web Workers 處理重型任務
class WorkerManager {
constructor() {
this.workers = new Map();
}
async createWorker(name, scriptPath) {
if (!this.workers.has(name)) {
const worker = new Worker(scriptPath);
this.workers.set(name, worker);
}
return this.workers.get(name);
}
async processData(workerName, data) {
const worker = await this.createWorker(workerName, './workers/data-processor.js');
return new Promise((resolve, reject) => {
worker.postMessage(data);
worker.onmessage = (event) => {
resolve(event.data);
};
worker.onerror = (error) => {
reject(error);
};
});
}
}
記憶體管理與垃圾回收
// 物件池模式減少垃圾回收
class ObjectPool {
constructor(createFn, resetFn, maxSize = 100) {
this.createFn = createFn;
this.resetFn = resetFn;
this.maxSize = maxSize;
this.pool = [];
this.active = new Set();
}
acquire() {
let obj;
if (this.pool.length > 0) {
obj = this.pool.pop();
} else {
obj = this.createFn();
}
this.active.add(obj);
return obj;
}
release(obj) {
if (this.active.has(obj)) {
this.active.delete(obj);
this.resetFn(obj);
if (this.pool.length < this.maxSize) {
this.pool.push(obj);
}
}
}
clear() {
this.pool.length = 0;
this.active.clear();
}
}
// 事件監聽器清理
class EventManager {
constructor() {
this.listeners = new Map();
this.abortController = new AbortController();
}
addEventListener(element, event, handler, options = {}) {
const finalOptions = {
...options,
signal: this.abortController.signal
};
element.addEventListener(event, handler, finalOptions);
// 記錄以便後續清理
const key = `${element.tagName}-${event}`;
if (!this.listeners.has(key)) {
this.listeners.set(key, []);
}
this.listeners.get(key).push({ element, event, handler });
}
cleanup() {
this.abortController.abort();
this.listeners.clear();
}
}
快取策略實作
HTTP 快取最佳化
// Service Worker 快取策略
const CACHE_NAME = 'the-lonesome-era-v1';
const STATIC_CACHE = 'static-v1';
const DYNAMIC_CACHE = 'dynamic-v1';
const STATIC_ASSETS = [
'/',
'/style.css',
'/app.js',
'/assets/imgs/blog_icon.png',
'/favicon.ico'
];
// 安裝階段:快取靜態資源
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE)
.then(cache => cache.addAll(STATIC_ASSETS))
.then(() => self.skipWaiting())
);
});
// 激活階段:清理舊快取
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
// 請求攔截:實施快取策略
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// 靜態資源:快取優先
if (STATIC_ASSETS.includes(url.pathname)) {
event.respondWith(cacheFirst(request));
}
// API 請求:網路優先
else if (url.pathname.startsWith('/api/')) {
event.respondWith(networkFirst(request));
}
// 圖片資源:快取優先,失敗時顯示預設圖片
else if (request.destination === 'image') {
event.respondWith(cacheFirstWithFallback(request, '/assets/imgs/placeholder.png'));
}
// 其他資源:網路優先
else {
event.respondWith(networkFirst(request));
}
});
// 快取優先策略
async function cacheFirst(request) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
try {
const networkResponse = await fetch(request);
const cache = await caches.open(DYNAMIC_CACHE);
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
console.error('Cache first strategy failed:', error);
throw error;
}
}
// 網路優先策略
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
const cache = await caches.open(DYNAMIC_CACHE);
cache.put(request, networkResponse.clone());
return networkResponse;
} catch (error) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
throw error;
}
}
瀏覽器快取優化
// 智能預載入
class IntelligentPreloader {
constructor() {
this.prefetchQueue = new Set();
this.observer = null;
this.init();
}
init() {
// 監聽滑鼠懸停事件
document.addEventListener('mouseover', this.handleMouseOver.bind(this));
// 監聽可視區域
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
{ rootMargin: '100px' }
);
// 觀察所有連結
document.querySelectorAll('a[href]').forEach(link => {
this.observer.observe(link);
});
}
handleMouseOver(event) {
if (event.target.tagName === 'A' && event.target.href) {
this.prefetchResource(event.target.href);
}
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting && entry.target.href) {
this.prefetchResource(entry.target.href);
}
});
}
prefetchResource(url) {
if (this.prefetchQueue.has(url)) return;
// 檢查網路狀況
if (navigator.connection && navigator.connection.effectiveType === 'slow-2g') {
return;
}
this.prefetchQueue.add(url);
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
}
}
// 資源優先級管理
class ResourcePriorityManager {
static setImagePriority(img, priority = 'auto') {
if ('fetchPriority' in HTMLImageElement.prototype) {
img.fetchPriority = priority;
}
}
static preloadCriticalResources() {
const criticalResources = [
{ href: '/style.css', as: 'style' },
{ href: '/app.js', as: 'script' },
{ href: '/assets/fonts/main.woff2', as: 'font', type: 'font/woff2', crossorigin: 'anonymous' }
];
criticalResources.forEach(resource => {
const link = document.createElement('link');
link.rel = 'preload';
Object.assign(link, resource);
document.head.appendChild(link);
});
}
}
效能監控與分析
Real User Monitoring (RUM)
// 效能監控類
class PerformanceMonitor {
constructor() {
this.metrics = {};
this.observer = null;
this.init();
}
init() {
// 監控 Core Web Vitals
this.observeLCP();
this.observeFID();
this.observeCLS();
// 監控其他指標
this.observeNavigationTiming();
this.observeResourceTiming();
}
observeLCP() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
this.metrics.lcp = lastEntry.startTime;
this.sendMetric('LCP', lastEntry.startTime);
});
observer.observe({ entryTypes: ['largest-contentful-paint'] });
}
}
observeFID() {
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
this.metrics.fid = entry.processingStart - entry.startTime;
this.sendMetric('FID', this.metrics.fid);
});
});
observer.observe({ entryTypes: ['first-input'] });
}
}
observeCLS() {
let clsValue = 0;
let clsEntries = [];
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
if (!entry.hadRecentInput) {
clsValue += entry.value;
clsEntries.push(entry);
}
});
this.metrics.cls = clsValue;
this.sendMetric('CLS', clsValue);
});
observer.observe({ entryTypes: ['layout-shift'] });
}
}
observeNavigationTiming() {
window.addEventListener('load', () => {
const navigation = performance.getEntriesByType('navigation')[0];
const metrics = {
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcp: navigation.connectEnd - navigation.connectStart,
ttfb: navigation.responseStart - navigation.requestStart,
download: navigation.responseEnd - navigation.responseStart,
domReady: navigation.domContentLoadedEventEnd - navigation.navigationStart,
loadComplete: navigation.loadEventEnd - navigation.navigationStart
};
Object.entries(metrics).forEach(([key, value]) => {
this.sendMetric(key.toUpperCase(), value);
});
});
}
sendMetric(name, value) {
// 發送到分析服務
if (navigator.sendBeacon) {
const data = JSON.stringify({
metric: name,
value: value,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: Date.now()
});
navigator.sendBeacon('/api/analytics/performance', data);
}
}
}
// 啟動效能監控
document.addEventListener('DOMContentLoaded', () => {
new PerformanceMonitor();
});
實戰優化案例
以 The Lonesome Era 網站為例,以下是具體的優化成果:
優化前後對比
指標
優化前
優化後
改善幅度
LCP
4.2s
1.8s
-57%
FID
180ms
45ms
-75%
CLS
0.25
0.05
-80%
總檔案大小
2.1MB
890KB
-58%
載入時間
5.8s
2.1s
-64%
關鍵優化措施
- 圖片優化:WebP 格式 + 響應式圖片 → 減少 70% 圖片大小
- 程式碼分割:動態載入非關鍵功能 → 減少初始載入 45%
- CSS 優化:關鍵 CSS 內聯 + 非關鍵 CSS 延遲載入
- Service Worker:智能快取策略 → 回訪速度提升 80%
- 預載入策略:關鍵資源預載入 + 智能預取
總結與建議
Web 效能優化是一個持續的過程,需要:
- 建立基準:使用 Lighthouse、WebPageTest 等工具測量
- 持續監控:實施 RUM 監控真實使用者體驗
- 逐步優化:從影響最大的問題開始
- 測試驗證:每次優化後都要測試效果
- 使用者優先:始終以使用者體驗為中心
記住:效能優化不是一次性的任務,而是開發流程的一部分。每一行程式碼、每一個資源都可能影響使用者體驗。
透過系統性的效能優化,我們不僅能提升使用者滿意度,還能改善 SEO 排名,最終實現更好的業務成果。
想要了解更多效能優化技巧?歡迎查看我們的其他技術文章,或者使用開發者工具分析 The Lonesome Era 網站的效能實作。