Material-UI范围Slider如何实现滑块样式固定不受滑动排序影响
解决方案
核心思路是将滑块样式与实体身份直接绑定,不再依赖代表位置顺序的data-index属性,避免滑块位置互换后样式跟随位置错乱。
修改逻辑
- 独立存储两个实体的对应值,不直接使用Slider默认返回的已排序数组
- 滑块拖动时通过值的变化量判断当前操作的实体,更新对应实体的数值
- 自定义Thumb组件中通过当前滑块值匹配所属实体,设置对应样式
完整可运行代码
import * as React from "react"; import PropTypes from "prop-types"; import Slider, { SliderThumb } from "@material-ui/core/Slider"; import { styled } from "@material-ui/core/styles"; import Typography from "@material-ui/core/Typography"; import Box from "@material-ui/core/Box"; import clsx from "clsx"; const EntitySlider = styled(Slider)(({ theme }) => ({ color: "#3a8589", height: 3, padding: "13px 0", "& .MuiSlider-thumb": { height: 27, width: 27, border: "1px solid currentColor", "&.entity-a-thumb": { backgroundColor: "#fff" }, "&.entity-b-thumb": { backgroundColor: "red" }, "&:hover": { boxShadow: "0 0 0 8px rgba(58, 133, 137, 0.16)" } }, "& .MuiSlider-track": { height: 3 }, "& .MuiSlider-rail": { color: theme.palette.mode === "dark" ? "#bfbfbf" : "#d8d8d8", opacity: theme.palette.mode === "dark" ? undefined : 1, height: 3 } })); function ThumbComponent(props) { const { children, className, entityAValue, entityBValue, ...other } = props; let extraClassName = ""; // 静止状态直接匹配值对应实体 if (other.value === entityAValue) { extraClassName = "entity-a-thumb"; } else if (other.value === entityBValue) { extraClassName = "entity-b-thumb"; } else { // 拖动过程中通过变化量匹配所属实体 const deltaA = Math.abs(other.value - entityAValue); const deltaB = Math.abs(other.value - entityBValue); extraClassName = deltaA < deltaB ? "entity-a-thumb" : "entity-b-thumb"; } return ( <SliderThumb {...other} className={clsx(className, extraClassName)}> {children} </SliderThumb> ); } ThumbComponent.propTypes = { children: PropTypes.node, entityAValue: PropTypes.number.isRequired, entityBValue: PropTypes.number.isRequired }; export default function CustomizedSlider() { // 独立存储两个实体的数值,和位置完全解耦 const [entityAValue, setEntityAValue] = React.useState(20); // 实体A固定白色 const [entityBValue, setEntityBValue] = React.useState(40); // 实体B固定红色 // Slider要求传入的值为升序数组,排序后传入 const sliderValue = React.useMemo(() => { return [entityAValue, entityBValue].sort((a, b) => a - b); }, [entityAValue, entityBValue]); const handleSliderChange = (e, newValue) => { // 计算两种对应关系的总变化量,判断当前拖动的实体 const deltaMatchAFirst = Math.abs(newValue[0] - entityAValue) + Math.abs(newValue[1] - entityBValue); const deltaMatchBFirst = Math.abs(newValue[0] - entityBValue) + Math.abs(newValue[1] - entityAValue); if (deltaMatchAFirst < deltaMatchBFirst) { setEntityAValue(newValue[0]); setEntityBValue(newValue[1]); } else { setEntityAValue(newValue[1]); setEntityBValue(newValue[0]); } }; return ( <Box sx={{ width: 320 }}> <Typography gutterBottom>Airbnb</Typography> <EntitySlider value={sliderValue} onChange={handleSliderChange} components={{ Thumb: ThumbComponent }} componentsProps={{ Thumb: { entityAValue: entityAValue, entityBValue: entityBValue } }} getAriaLabel={(index) => index === 0 ? "Minimum price" : "Maximum price" } /> </Box> ); }
方案说明
- 完全脱离对
data-index的依赖,滑块样式只和所属实体绑定,位置互换不会影响样式 - 拖动过程中通过变化量匹配可以准确识别所属实体,不会出现拖动时样式跳动的问题
- 兼容两个滑块值相等的极端场景,样式不会冲突
内容的提问来源于stack exchange,提问作者code_abhi
相关产品推荐
相关产品推荐

