如何在MUI Autocomplete的Avatar组件中实现图片懒加载?
解决方案
针对MUI Autocomplete加载大量Avatar图片时的不必要加载问题,有两种实用方案:
方案一:使用虚拟列表(推荐)
Autocomplete默认会渲染所有选项,导致500+个Avatar一次性加载。虚拟列表只会渲染当前视口内的选项,从根源上避免无效图片请求,同时提升下拉菜单的渲染性能。
实现步骤(结合react-window)
- 安装依赖:
npm install react-window
- 代码示例:
import React from 'react'; import Autocomplete from '@mui/material/Autocomplete'; import TextField from '@mui/material/TextField'; import Avatar from '@mui/material/Avatar'; import { FixedSizeList as List } from 'react-window'; // 你的500+条选项数据 const options = Array.from({ length: 500 }, (_, i) => ({ id: i, name: `Option ${i}`, avatarUrl: `https://example.com/avatar/${i}.jpg` })); // 渲染单个选项 const renderOption = (props, option) => { const { style } = props; return ( <div style={{ ...style, display: 'flex', alignItems: 'center', padding: '8px 16px', cursor: 'pointer' }} > <Avatar src={option.avatarUrl} alt={option.name} /> <span style={{ marginLeft: 12 }}>{option.name}</span> </div> ); }; // 自定义列表容器,集成虚拟列表 const ListboxComponent = (props) => { const { children, ...other } = props; return ( <div {...other} style={{ maxHeight: 400, overflow: 'auto', borderTop: '1px solid #e0e0e0' }} > <List height={400} itemCount={options.length} itemSize={48} // 每个选项的高度 width="100%" > {renderOption} </List> </div> ); }; export default function VirtualizedAutocomplete() { return ( <Autocomplete options={options} renderInput={(params) => ( <TextField {...params} label="选择选项" variant="outlined" /> )} ListboxComponent={ListboxComponent} /> ); }
原理
虚拟列表FixedSizeList只会渲染当前滚动视口内的选项,只有可见的Avatar会被创建并加载图片,未进入视口的选项不会生成DOM节点,自然不会触发图片请求。
方案二:基于Intersection Observer的懒加载
如果不想引入虚拟列表依赖,可以用浏览器原生的Intersection Observer API,监听Avatar是否进入下拉菜单的视口,仅在可见时加载真实图片。
代码示例
import React, { useEffect, useRef } from 'react'; import Autocomplete from '@mui/material/Autocomplete'; import TextField from '@mui/material/TextField'; import Avatar from '@mui/material/Avatar'; const options = Array.from({ length: 500 }, (_, i) => ({ id: i, name: `Option ${i}`, avatarUrl: `https://example.com/avatar/${i}.jpg` })); // 自定义懒加载Avatar组件 const LazyAvatar = ({ src, alt }) => { const imgRef = useRef(null); // 空白占位图(避免加载失败的图标) const placeholderSrc = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; useEffect(() => { // 获取Autocomplete的下拉菜单容器作为观察根节点 const listboxRoot = document.querySelector('.MuiAutocomplete-listbox'); if (!listboxRoot || !imgRef.current) return; const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // 元素可见时加载真实图片 imgRef.current.src = src; // 停止观察该元素 observer.unobserve(entry.target); } }); }, { root: listboxRoot, rootMargin: '20px' } // 提前20px开始加载 ); observer.observe(imgRef.current); // 组件卸载时清理观察器 return () => observer.unobserve(imgRef.current); }, [src]); return <Avatar ref={imgRef} src={placeholderSrc} alt={alt} />; }; export default function LazyLoadAutocomplete() { return ( <Autocomplete options={options} renderOption={(props, option) => ( <li {...props} style={{ display: 'flex', alignItems: 'center', padding: '8px 16px' }} > <LazyAvatar src={option.avatarUrl} alt={option.name} /> <span style={{ marginLeft: 12 }}>{option.name}</span> </li> )} renderInput={(params) => ( <TextField {...params} label="选择选项" variant="outlined" /> )} /> ); }
原理
- 初始给Avatar设置空白占位图,不加载真实图片
- 通过Intersection Observer监听Avatar是否进入下拉菜单的视口范围
- 当元素进入视口时,替换为真实图片地址并停止观察
内容的提问来源于stack exchange,提问作者suman j
相关产品推荐
相关产品推荐

