Material UI Card 组件族实战从基础卡片、媒体展示到整卡交互与 Active 状态样式【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui本篇技术指南基于 Material UI 官方 Card 文档cards.md系统讲解 Card 及其配套组件CardContent、CardHeader、CardMedia、CardActions、CardActionArea的完整用法。读完本文你将能够构建基础/描边卡片、用Collapse实现可展开的复杂交互卡片、正确选择CardMedia的两种媒体渲染模式、用CardActionArea让整张卡片可点击并用data-active属性定制卡片的选中态样式——同时结合 packages/mui-material/src/Card 等源码目录理解每个组件的渲染机制、默认样式与可定制类名。Card 组件族总览在 Material Design 规范中卡片Card是承载单一主题的内容与操作的表面。Material UI 用一个主容器加若干配套组件来覆盖各种卡片场景Card表面层容器负责把相关组件分组基于Paper实现CardContent卡片正文内容的包装器CardHeader可选的头部包装器支持头像avatar、操作action、标题title、副标题subheader等插槽CardMedia可选的媒体容器用于展示图片、视频等CardActions可选的按钮组包装器通常位于卡片底部CardActionArea可选的交互区包装器让用户与卡片的指定区域通常是整卡进行交互。在仓库中这六个组件分别位于独立源码目录packages/mui-material/src/Cardpackages/mui-material/src/CardActionAreapackages/mui-material/src/CardActionspackages/mui-material/src/CardContentpackages/mui-material/src/CardHeaderpackages/mui-material/src/CardMedia官方文档也提醒虽然卡片可以容纳多个操作、UI 控件和溢出菜单但应克制使用——卡片的设计初衷是通向更复杂、更详细信息的入口点。基础卡片Card CardContent最小可用的卡片只需Card与CardContentimport Card from mui/material/Card; import CardContent from mui/material/CardContent;官方示例 BasicCard.tsx 展示了完整的每日单词卡片结构import Box from mui/material/Box; import Card from mui/material/Card; import CardActions from mui/material/CardActions; import CardContent from mui/material/CardContent; import Button from mui/material/Button; import Typography from mui/material/Typography; const bull ( Box componentspan sx{{ display: inline-block, mx: 2px, transform: scale(0.8) }} • /Box ); export default function BasicCard() { return ( Card sx{{ minWidth: 275 }} CardContent Typography gutterBottom sx{{ color: text.secondary, fontSize: 14 }} Word of the Day /Typography Typography varianth5 componentdiv be{bull}nev{bull}o{bull}lent /Typography Typography sx{{ color: text.secondary, mb: 1.5 }}adjective/Typography Typography variantbody2 well meaning and kindly. br / {a benevolent smile} /Typography /CardContent CardActions Button sizesmallLearn More/Button /CardActions /Card ); }结合源码可以确认几个默认行为Card 是 Paper 的特化。Card.js 中根节点由styled(Paper)派生仅追加一条overflow: hidden样式这也是后续 CardMedia 图片被裁切、焦点环需要改为内缩的原因。因此 Card 继承 Paper 的variant、elevation、square、component等全部属性。raised属性等价于 elevation 8。从 Card.js 可见raised{true}时向 Paper 传入elevation{8}其 PropTypes 还内置了一条校验raised与variantoutlined同时使用时会告警组合无效。CardContent 的默认内边距。CardContent.js 中根节点固定padding: 16且当它是最后一个子元素时paddingBottom增大为24保证卡片底部留白更舒适。描边卡片variantoutlined设置variantoutlined即可渲染描边卡片。该属性来自继承自 Paper 的variant可选值elevated默认/outlined/filled。官方示例 OutlinedCard.tsx 将卡片正文抽成可复用片段const card ( React.Fragment CardContent Typography gutterBottom sx{{ color: text.secondary, fontSize: 14 }} Word of the Day /Typography {/* ... 同 BasicCard 的正文 ... */} /CardContent CardActions Button sizesmallLearn More/Button /CardActions /React.Fragment ); export default function OutlinedCard() { return ( Box sx{{ minWidth: 275 }} Card variantoutlined{card}/Card /Box ); }由于 Card 只是 Paper 的薄封装见 Card.d.ts 中CardOwnProps extends DistributiveOmitPaperOwnProps, classes所有 Paper 支持的外观定制手段——elevation调整、sx覆写、主题中的components: { MuiCard: { defaultProps } }——对 Card 同样生效。复杂交互可展开的食谱卡片官方示例 RecipeReviewCard.tsx 展示了桌面端卡片的典型进阶形态点击右下方的展开箭头chevron卡片内容区平滑展开显示完整食谱。其结构为CardHeader头像 标题 更多操作CardMediaCardContentCardActionsCollapse。核心代码如下import { styled } from mui/material/styles; import Card from mui/material/Card; import CardHeader from mui/material/CardHeader; import CardMedia from mui/material/CardMedia; import CardContent from mui/material/CardContent; import CardActions from mui/material/CardActions; import Collapse from mui/material/Collapse; import Avatar from mui/material/Avatar; import IconButton, { IconButtonProps } from mui/material/IconButton; import Typography from mui/material/Typography; import { red } from mui/material/colors; import FavoriteIcon from mui/icons-material/Favorite; import ShareIcon from mui/icons-material/Share; import ExpandMoreIcon from mui/icons-material/ExpandMore; import MoreVertIcon from mui/icons-material/MoreVert; // 通过 styled 让展开箭头随状态旋转 180 度 interface ExpandMoreProps extends IconButtonProps { expand: boolean; } const ExpandMore styled((props: ExpandMoreProps) { const { expand, ...other } props; return IconButton {...other} /; })(({ theme }) ({ marginLeft: auto, transition: theme.transitions.create(transform, { duration: theme.transitions.duration.shortest, }), variants: [ { props: ({ expand }) !expand, style: { transform: rotate(0deg) }, }, { props: ({ expand }) !!expand, style: { transform: rotate(180deg) }, }, ], })); export default function RecipeReviewCard() { const [expanded, setExpanded] React.useState(false); const handleExpandClick () { setExpanded(!expanded); }; return ( Card sx{{ maxWidth: 345 }} CardHeader avatar{ Avatar sx{{ bgcolor: red[500] }} aria-labelrecipe R /Avatar } action{ IconButton aria-labelsettings MoreVertIcon / /IconButton } titleShrimp and Chorizo Paella subheaderSeptember 14, 2016 / CardMedia componentimg height194 image/static/images/cards/paella.jpg altPaella dish / CardContent Typography variantbody2 sx{{ color: text.secondary }} This impressive paella is a perfect party dish ... /Typography /CardContent CardActions disableSpacing IconButton aria-labeladd to favorites FavoriteIcon / /IconButton IconButton aria-labelshare ShareIcon / /IconButton ExpandMore expand{expanded} onClick{handleExpandClick} aria-expanded{expanded} aria-labelshow more ExpandMoreIcon / /ExpandMore /CardActions Collapse in{expanded} timeoutauto unmountOnExit CardContent Typography sx{{ marginBottom: 2 }}Method:/Typography Typography sx{{ marginBottom: 2 }} Heat 1/2 cup of the broth in a pot until simmering ... /Typography {/* 完整食谱步骤见 demo 源文件 */} /CardContent /Collapse /Card ); }这里有几个可复用的交互技巧ExpandMore用styled(IconButton)做条件旋转箭头是否展开通过expand布尔 prop 传入用variants声明rotate(0deg)/rotate(180deg)两态过渡时长取自theme.transitions.duration.shortestCollapse in{expanded} timeoutauto unmountOnExit展开动画时长由组件自动计算动画结束后从 DOM 卸载避免隐藏内容仍占用布局与可访问性树CardActions disableSpacingdisableSpacing关闭卡片底部按钮组默认的 8px 内边距与控件间距让图标按钮更紧凑。这一点可以从 CardActions.js 得到印证根节点默认display: flex; align-items: center; padding: 8且只有当disableSpacing为false时才应用spacing类给后续兄弟元素加margin-left: 8CardHeader的插槽化结构从 cardHeaderClasses.ts 可见CardHeader 暴露root、avatar、action、content、title、subheader六个样式槽分别对应示例中的avatar、action、title、subheader属性需要精细定制头部时可按这些类名覆写。媒体展示CardMedia 的两种渲染模式MediaCard.tsx 演示了用图片强化卡片内容的标准用法export default function MediaCard() { return ( Card sx{{ maxWidth: 345 }} CardMedia sx{{ height: 140 }} image/static/images/cards/contemplative-reptile.jpg titlegreen iguana / CardContent Typography gutterBottom varianth5 componentdiv Lizard /Typography Typography variantbody2 sx{{ color: text.secondary }} Lizards are a widespread group of squamate reptiles, with over 6,000 species, ranging across all continents except Antarctica /Typography /CardContent CardActions Button sizesmallShare/Button Button sizesmallLearn More/Button /CardActions /Card ); }默认模式div 背景图。从 CardMedia.js 可以看到CardMedia根节点默认是div样式为display: block; background-size: cover; background-repeat: no-repeat; background-position: center。当传入image且component仍是div时源码会把image拼进内联backgroundImage: url(...)同时给根节点加上roleimg以提升可访问性CardMedia.js。局限与替代component属性。背景图方案在某些场景并不合适——比如需要显示视频、或需要响应式img。此时应使用component属性让CardMedia渲染真实媒体元素官方示例 ImgMediaCard.tsxCard sx{{ maxWidth: 345 }} CardMedia componentimg altgreen iguana height140 image/static/images/cards/contemplative-reptile.jpg / {/* CardContent / CardActions 同上 */} /Card源码中的判定逻辑值得注意CardMedia.jsMEDIA_COMPONENTS [video, audio, picture, iframe, img]当component是这些媒体元素之一时isMediaComponentimage会被转换为真实的src属性并附带width: 100%IMAGE_COMPONENTS [picture, img]当component是picture/img时isImageComponent追加object-fit: cover与背景图模式保持相同的裁切观感PropTypes 校验要求children、image、src、component至少提供一个否则会抛出 Eitherchildren,image,srcorcomponentprop must be specified 错误文档特别提示背景图模式下调用方必须显式指定height如sx{{ height: 140 }}否则图片不可见——因为div没有固有高度。这也是MediaCard示例中height写在sx里的原因。主要操作CardActionArea 让整卡可交互卡片经常需要让用户点击整张卡片表面来触发主操作展开、跳转详情等。用CardActionArea包裹内容即可实现官方示例 ActionAreaCard.tsximport CardActionArea from mui/material/CardActionArea; // 其余导入同 MediaCard export default function ActionAreaCard() { return ( Card sx{{ maxWidth: 345 }} CardActionArea CardMedia componentimg height140 image/static/images/cards/contemplative-reptile.jpg altgreen iguana / CardContent Typography gutterBottom varianth5 componentdiv Lizard /Typography Typography variantbody2 sx{{ color: text.secondary }} Lizards are a widespread group of squamate reptiles ... /Typography /CardContent /CardActionArea /Card ); }从 CardActionArea.js 可以确认它的实现细节根节点基于ButtonBase因此自带onClick、键盘可达、focusVisible等完整的按钮语义而不仅仅是可点击的 divdisplay: block、width: 100%、border-radius: inherit让它铺满 Card 内部并继承 Card 的圆角后者还附带注释指出是为修复 Safari 下的圆角继承问题。悬浮反馈通过独立覆盖层实现CardActionArea内部渲染一个FocusHighlight槽span绝对定位铺满、pointer-events: none、background-color: currentcolor。鼠标悬停时该层 opacity 取theme.palette.action.hoverOpacity键盘聚焦focus visible时取palette.action.focusOpacity透明度变化使用theme.transitions.duration.short过渡在media (hover: none)触屏设备下悬浮层透明度固定为 0。焦点环被设计为内缩inset源码注释明确写道 Card sets overflow:hidden, which clips an outset ring——因为 Card 根节点overflow: hidden会裁掉外扩的焦点环所以启用theme.focusVisible时焦点环向内缩进 1px避免视觉被截断。补充操作要与主操作区分离。卡片还可以提供与主操作并列的补充动作这些动作必须放在CardActionArea之外以避免事件冒泡重叠。官方示例 MultiActionAreaCard.tsx 的结构是Card sx{{ maxWidth: 345 }} CardActionArea CardMedia componentimg height140 image... altgreen iguana / CardContent.../CardContent /CardActionArea {/* 补充操作放在 CardActionArea 之外避免与主点击区域事件重叠 */} CardActions Button sizesmall colorprimary Share /Button /CardActions /CardUI 控件底部媒体控制卡片补充操作在卡片中通常以图标、文字和 UI 控件形式明确给出并常规放置在卡片底部。MediaControlCard.tsx 展示了音乐播放器式卡片左侧标题 控制按钮纵向排列右侧是专辑封面。const theme useTheme(); Card sx{{ display: flex }} Box sx{{ display: flex, flexDirection: column }} CardContent sx{{ flex: 1 0 auto }} Typography componentdiv varianth5 Live From Space /Typography Typography variantsubtitle1 componentdiv sx{{ color: text.secondary }} Mac Miller /Typography /CardContent Box sx{{ display: flex, alignItems: center, pl: 1, pb: 1 }} IconButton aria-labelprevious {theme.direction rtl ? SkipNextIcon / : SkipPreviousIcon /} /IconButton IconButton aria-labelplay/pause PlayArrowIcon sx{{ height: 38, width: 38 }} / /IconButton IconButton aria-labelnext {theme.direction rtl ? SkipPreviousIcon / : SkipNextIcon /} /IconButton /Box /Box CardMedia componentimg sx{{ width: 151 }} image/static/images/cards/live-from-space.jpg altLive from space album cover / /Card这个示例体现了两个要点Card 根节点可以直接用sx变成 flex 布局容器配合CardContent的flex: 1 0 auto让标题区撑满剩余高度底部控制条自然沉底RTL 适配上一首/下一首图标依据theme.direction互换这是 Material UI 对右到左语言的内置约定控件类卡片应当遵循。另外可以回顾 CardActions.js 的默认布局display: flexalign-items: centerpadding: 8未禁用 spacing 时控件之间自动保持 8px 间距——因此底部放两个sizesmall的Button如 MediaCard.tsx 中的 Share / Learn More即可获得符合规范的间距。Active 状态样式data-active 属性 [data-active] 选择器当卡片承担选中项语义如选择列表时需要为CardActionArea定制激活态。官方推荐做法是在CardActionArea上挂data-active属性再用[data-active]选择器应用样式。官方示例 SelectActionCard.tsxfunction SelectActionCard() { const [selectedCard, setSelectedCard] React.useState(0); return ( Box sx{{ width: 100%, display: grid, gridTemplateColumns: repeat(auto-fill, minmax(min(200px, 100%), 1fr)), gap: 2, }} {cards.map((card, index) ( Card key{card.id} CardActionArea onClick{() setSelectedCard(index)} contenteditable="false">【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考