
本文详解如何在 Material UI(MUI)中正确为 ::-webkit-scrollbar-thumb 添加 CSS 动画,解决使用 sx 属性定义 @keyframes 和 CSS 自定义属性时动画无缓动、瞬时切换的问题。
本文详解如何在 material ui(mui)中正确为 `::-webkit-scrollbar-thumb` 添加 css 动画,解决使用 `sx` 属性定义 `@keyframes` 和 css 自定义属性时动画无缓动、瞬时切换的问题。
在 Material UI 中,开发者常希望通过 sx 属性结合 CSS 自定义属性(如 --var1)与 @keyframes 实现滚动条的渐显/渐隐效果。但实践中会发现:即使指定了 ease-in-out 缓动函数,动画仍表现为“突显突隐”——即无过渡、无渐变,本质是动画未被浏览器正确触发或应用。
根本原因在于:
✅ @keyframes 在 sx 中无法被 MUI 的样式系统识别并注入 ;
✅ CSS 自定义属性(--var1)无法通过 @property 声明在 sx 中生效(该语法仅支持原生 CSS 或 makeStyles/styled 等传统方案);
✅ &::-webkit-scrollbar-thumb 的 background: var(--var1) 是静态计算值,不响应 --var1 的运行时变更动画——除非配合 @property + 支持的浏览器(Chrome 110+),而 MUI 的 sx 当前不支持该声明。
✅ 正确解法:回归标准 CSS 注入机制,使用 makeStyles(或 styled / emotion)定义全局 @keyframes 和 @property,并通过动态类名控制动画状态。
以下是可直接落地的专业实现(适配 MUI v5,兼容 Chrome/Edge):
import React, { useEffect, useRef } from 'react';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import { makeStyles, createTheme, ThemeProvider } from '@mui/styles';
// ✅ 关键:在 makeStyles 中定义 @keyframes 和 @property
const useStyles = makeStyles((theme) => ({
showsRow: {
border: theme.border || '1px solid #e0e0e0',
maxHeight: 'min(30%, 400px)',
padding: 'min(1%, 20px) 0',
borderRadius: 0,
transition: 'background-color 0.3s ease-in-out',
backgroundColor: 'rgba(0, 0, 0, 0)',
},
scrollContainer: {
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
gap: 'min(3%, 20px)',
width: '100%',
overflowX: 'scroll' as const,
overflowY: 'hidden',
padding: '0 min(1%, 20px)',
// ✅ 滚动条基础样式(静态)
'&::-webkit-scrollbar': {
height: '0.8em',
background: 'transparent',
},
'&::-webkit-scrollbar-thumb': {
background: 'var(--scroll-thumb-color, rgba(0, 0, 0, 0.3))',
borderRadius: '1em',
// ⚠️ 注意:此处依赖 --scroll-thumb-color,需由动画驱动
},
},
// ✅ 动画类:显式分离 fadeIn/fadeOut,避免冲突
fadeIn: {
animation: '$fadeIn 0.3s ease-in-out forwards',
},
fadeOut: {
animation: '$fadeOut 0.3s ease-in-out forwards',
},
// ✅ 全局 keyframes(必须在此处定义)
'@keyframes fadeIn': {
'0%': { '--scroll-thumb-color': 'rgba(0, 0, 0, 0)' },
'100%': { '--scroll-thumb-color': 'rgba(0, 0, 0, 0.3)' },
},
'@keyframes fadeOut': {
'0%': { '--scroll-thumb-color': 'rgba(0, 0, 0, 0.3)' },
'100%': { '--scroll-thumb-color': 'rgba(0, 0, 0, 0)' },
},
// ✅ 必须声明 @property(Chrome 110+ 支持动画自定义属性)
'@property --scroll-thumb-color': {
syntax: '<color>',
inherits: true,
initialValue: 'rgba(0, 0, 0, 0)',
},
}));
const ShowsRow = ({ children, genre }: { children: React.ReactNode; genre: string }) => {
const classes = useStyles();
const scrollRef = useRef<htmldivelement>(null);
// ✅ 使用 ref + classList 精准控制动画(避免重复添加)
const handleMouseEnter = () => {
if (scrollRef.current) {
scrollRef.current.classList.remove(classes.fadeOut);
scrollRef.current.classList.add(classes.fadeIn);
}
};
const handleMouseLeave = () => {
if (scrollRef.current) {
scrollRef.current.classList.remove(classes.fadeIn);
scrollRef.current.classList.add(classes.fadeOut);
}
};
return (
<paper classname="{classes.showsRow}" elevation="{2}" onmouseenter="{handleMouseEnter}" onmouseleave="{handleMouseLeave}"><typography variant="h5" sx="{{" fontweight: px:>
{genre}
</typography><box ref="{scrollRef}" classname="{classes.scrollContainer}" sx="{{" min calc class>
{children}
</box></paper>
);
};
// ⚠️ 注意:若项目已升级至 MUI v5.15+,推荐改用 `styled` + `keyframes`(更现代)
// import { styled, keyframes } from '@mui/material/styles';
// const fadeIn = keyframes`...`;
export default ShowsRow;</htmldivelement></color>
? 关键注意事项:
- 浏览器兼容性:@property 仅在 Chrome 110+ / Edge 110+ 支持动画;旧版需降级为 opacity 或 transform 动画(例如缩放滚动条 thumb);
- 性能提示:避免对 ::-webkit-scrollbar 大量使用 box-shadow 或 filter,易引发重绘卡顿;
- 无障碍友好:确保滚动条始终可操作(不要完全隐藏导致键盘用户无法滚动);
- MUI v5.15+ 替代方案:优先使用 @emotion/react 的 keyframes + styled,语义更清晰且支持 SSR。
? 总结:Material UI 的 sx 属性强大但有边界——它不处理 @keyframes 注入和 @property 声明。要实现滚动条动画,必须借助 CSS-in-JS 的完整能力(makeStyles / styled),并将动画控制权交给 DOM 类名与 ref,而非试图在 sx 中“硬编码”动画逻辑。这是工程实践中的经典权衡:简洁性让位于可控性与兼容性。










