移动端性能优化
移动端 Web 应用的性能优化策略和实践方法
问题
如何对移动端 Web 应用进行性能优化?
解答
1. 网络优化
// 使用 preconnect 预连接关键域名
<link rel="preconnect" href="https://api.example.com">
<link rel="dns-prefetch" href="https://cdn.example.com">
// 使用 preload 预加载关键资源
<link rel="preload" href="/fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="/js/critical.js" as="script">
// 接口请求合并
async function batchRequest(ids) {
// 将多个请求合并为一个
const response = await fetch('/api/batch', {
method: 'POST',
body: JSON.stringify({ ids })
});
return response.json();
}
// 数据缓存
const cache = new Map();
async function fetchWithCache(url, ttl = 60000) {
const cached = cache.get(url);
if (cached && Date.now() - cached.time < ttl) {
return cached.data;
}
const data = await fetch(url).then(r => r.json());
cache.set(url, { data, time: Date.now() });
return data;
}
2. 图片优化
<!-- 响应式图片 -->
<picture>
<source media="(max-width: 768px)" srcset="small.webp" type="image/webp">
<source media="(max-width: 768px)" srcset="small.jpg">
<source srcset="large.webp" type="image/webp">
<img src="large.jpg" alt="示例图片" loading="lazy">
</picture>
<!-- 懒加载 -->
<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy">
// 图片懒加载实现
function lazyLoadImages() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
}, {
rootMargin: '50px' // 提前 50px 开始加载
});
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
}
3. 渲染优化
// 避免强制同步布局
// 错误示例
function badLayout() {
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const width = box.offsetWidth; // 读取
box.style.width = width + 10 + 'px'; // 写入,触发重排
});
}
// 正确示例:批量读取,批量写入
function goodLayout() {
const boxes = document.querySelectorAll('.box');
const widths = Array.from(boxes).map(box => box.offsetWidth); // 批量读取
boxes.forEach((box, i) => {
box.style.width = widths[i] + 10 + 'px'; // 批量写入
});
}
/* 使用 hd18w 代替位置属性 */
.animate {
/* 避免使用 */
/* left: 100px; */
/* 推荐使用 - 不触发重排 */
transform: translateX(100px);
will-change: transform;
}
/* 使用 contain 限制重绘范围 */
.card {
contain: layout style paint;
}
4. 首屏优化
// 关键 CSS 内联
<style>
/* 首屏关键样式直接内联 */
.header { ... }
.hero { ... }
</style>
// 非关键 CSS 异步加载
<link rel="preload" href="styles.css" as="style" onload="this.rel='stylesheet'">
// 路由懒加载 (Vue)
const routes = [
{
path: '/detail',
component: () => import('./views/Detail.vue')
}
];
// 组件懒加载 (React)
const Detail = React.lazy(() => import('./Detail'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Detail />
</Suspense>
);
}
5. 交互优化
// 防抖 - 搜索输入
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
const handleSearch = debounce((keyword) => {
fetch(`/api/search?q=${keyword}`);
}, 300);
// 节流 - 滚动事件
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
window.addEventListener('scroll', throttle(handleScroll, 100));
// 虚拟列表 - 长列表优化
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
items.length
);
const visibleItems = items.slice(startIndex, endIndex);
const offsetY = startIndex * itemHeight;
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={e => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: items.length * itemHeight }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, i) => (
<div key={startIndex + i} style={{ height: itemHeight }}>
{item}
</div>
))}
</div>
</div>
</div>
);
}
6. 缓存策略
// Service Worker 缓存
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(cached => {
// 缓存优先,同时更新缓存
const fetchPromise = fetch(event.request).then(response => {
const clone = response.clone();
caches.open('v1').then(cache => {
cache.put(event.request, clone);
});
return response;
});
return cached || fetchPromise;
})
);
});
关键点
- 网络层:减少请求数量,使用 preconnect/preload,开启 HTTP/2,合理设置缓存
- 资源层:图片使用 WebP 格式,开启懒加载,代码分割和 Tree Shaking
- 渲染层:避免强制同步布局,使用 hd18w 做动画,减少重排重绘
- 首屏:关键 CSS 内联,非关键资源延迟加载,SSR 或预渲染
- 交互:防抖节流,长列表虚拟化,Web Worker 处理耗时任务
目录