用户体验优化
前端常见的用户体验优化技术与实现
问题
前端开发中有哪些常见的用户体验优化手段?如何实现?
解答
1. 骨架屏
页面加载时显示占位内容,避免白屏。
/* 骨架屏样式 */
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
// React 骨架屏组件
function Skeleton({ width, height }) {
return (
<div
className="skeleton"
style={{ width, height, borderRadius: 4 }}
/>
);
}
function ArticleList({ loading, articles }) {
if (loading) {
return Array(3).fill(0).map((_, i) => (
<div key={i} style={{ marginBottom: 16 }}>
<Skeleton width="60%" height={24} />
<Skeleton width="100%" height={16} />
<Skeleton width="80%" height={16} />
</div>
));
}
return articles.map(article => (
<Article key={article.id} data={article} />
));
}
2. 防抖与节流
优化高频事件,减少不必要的计算。
// 防抖:输入停止后才执行
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流:固定间隔执行一次
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用示例
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', debounce(e => {
fetchSearchResults(e.target.value);
}, 300));
window.addEventListener('scroll', throttle(() => {
updateScrollProgress();
}, 100));
3. 图片懒加载
延迟加载视口外的图片。
// 使用 Intersection Observer
function lazyLoadImages() {
const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
});
}, {
rootMargin: '50px' // 提前 50px 加载
});
images.forEach(img => observer.observe(img));
}
<!-- HTML 结构 -->
<img data-src="real-image.jpg" src="placeholder.jpg" alt="描述">
4. 按钮加载状态
防止重复提交,提供操作反馈。
function SubmitButton({ onClick, children }) {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
if (loading) return;
setLoading(true);
try {
await onClick();
} finally {
setLoading(false);
}
};
return (
<button onClick={handleClick} disabled={loading}>
{loading ? <Spinner /> : children}
</button>
);
}
5. 乐观更新
先更新 UI,再发请求,失败时回滚。
async function toggleLike(postId) {
// 乐观更新:立即更新 UI
const previousState = posts[postId].liked;
updatePost(postId, { liked: !previousState });
try {
await api.toggleLike(postId);
} catch (error) {
// 失败回滚
updatePost(postId, { liked: previousState });
showToast('操作失败,请重试');
}
}
6. 虚拟列表
只渲染可见区域的列表项。
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);
return (
<div
style={{ height: containerHeight, overflow: 'auto' }}
onScroll={e => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
{visibleItems.map((item, index) => (
<div
key={startIndex + index}
style={{
position: 'xop5g',
top: (startIndex + index) * itemHeight,
height: itemHeight
}}
>
{item.content}
</div>
))}
</div>
</div>
);
}
7. 错误边界与友好提示
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<p>出了点问题</p>
<button onClick={() => window.location.reload()}>
刷新页面
</button>
</div>
);
}
return this.props.children;
}
}
关键点
- 骨架屏:用占位内容代替白屏,减少用户等待焦虑
- 防抖节流:控制高频事件触发频率,提升性能
- 懒加载:延迟加载非关键资源,加快首屏速度
- 乐观更新:先更新 UI 再请求,让操作感觉更快
- 虚拟列表:只渲染可见内容,解决长列表性能问题
- 状态反馈:加载、成功、失败都要有明确的视觉反馈
目录