实现:
hooks实现
动画:
提示框渐变显示
参数:
disabled: PropTypes.bool, 禁用
onChange: PropTypes.func, 拉动回调,第一个参数是进度数值,防抖默认200毫秒
value: PropTypes.number, 初始进度值,0-10
sliderHeight:PropTypes.string, 滑块高度
sliderWdith: PropTypes.string, 滑块宽度
sliderColor: PropTypes.string, 滑块背景色
sliderBorderRadius:PropTypes.string, 滑块圆角
progressColor:PropTypes.string, 进度条背景色
circleSize:PropTypes.string, 按钮大小
circleColor:PropTypes.string, 按钮颜色
circleBorderColor:PropTypes.string, 按钮圆角
tooltipSize: PropTypes.string, 提示框大小
tooltipColor:PropTypes.string, 提示框颜色
tooltipFontColor: PropTypes.string, 提示框字体颜色
tooltipFontSize: PropTypes.string, 提示框字体大小
tooltipBorderRadius:PropTypes.string,提示框圆角
效果图:
代码示例:
使用:
import React,{Component,lazy,Suspense} from 'react'
// import Slider from './slider/slider'
const Slider=lazy(()=>import('./slider/slider'))
class App extends Component{
state={
}
_onChange(val)
{
console.log(val);
}
render()
{
return(
<div>
<Suspense fallback={<div>等待中</div>}>
<Slider disabled value={10} onChange={this._onChange.bind(this)}></Slider>
</Suspense>
</div>
)
}
}
export default App
slider.jsx:
import React,{useState,useRef,useCallback,useEffect,useMemo} from 'react';
import PropTypes from 'prop-types';
import './slider.css'
function App(props){
const [startX, setStartX] = useState(0);
const [sliding, setSliding] = useState(false);
const [sliderWidth, setSliderWidth] = useState(0);
const [lastWidth, setLastWidth] = useState(0); //当前进度条对应的值
const [disabled, setDisabled] = useState(props.disabled);
const [showTip, setShowTip] = useState(false);
let [timer, setTimer] = useState(null);
const sliderf = useRef(null);
useEffect(() => {
setSliderWidth(sliderf.current.clientWidth);
//根据初始值设置进度条、按钮位置
setLastWidth(props.value / 100);
_setProgress(props.value / 100);
const {sliderHeight,sliderWidth,sliderColor,sliderBorderRadius,tooltipBorderRadius,progressColor,progressWidth,circleBorderColor,circleColor,circleLeft,circleSize,tooltipColor,tooltipFontColor,tooltipFontSize,tooltipSize } = props;
//样式初始值设置
sliderHeight && sliderf.current.style.setProperty('--sliderHeight', sliderHeight)
sliderWidth && sliderf.current.style.setProperty('--sliderWidth', sliderWidth)
sliderColor && sliderf.current.style.setProperty('--sliderColor', sliderColor)
sliderBorderRadius && sliderf.current.style.setProperty('--sliderBorderRadius', sliderBorderRadius)
progressColor && sliderf.current.style.setProperty('--progressColor', progressColor)
circleSize && sliderf.current.style.setProperty('--circleSize', circleSize)
circleColor && sliderf.current.style.setProperty('--circleColor', circleColor)
circleBorderColor && sliderf.current.style.setProperty('--circleBorderColor', circleBorderColor)
tooltipSize && sliderf.current.style.setProperty('--tooltipSize', tooltipSize)
tooltipColor && sliderf.current.style.setProperty('--tooltipColor', tooltipColor)
tooltipFontColor && sliderf.current.style.setProperty('--tooltipFontColor', tooltipFontColor)
tooltipFontSize && sliderf.current.style.setProperty('--tooltipFontSize', tooltipFontSize)
tooltipBorderRadius && sliderf.current.style.setProperty('--tooltipBorderRadius', tooltipBorderRadius)
},[sliderWidth])
//滑动相关逻辑,依赖项改变重新创建函数,同步状态
//sliding,startX,sliderWidth改变时才更新函数
//sliding,startX只会在鼠标按下和抬起改变,所以useEffect每轮只会执行2次
useEffect(() => {
const _onmousemove = (e) => {
if (sliding&&!disabled) {
let moveX = e.pageX;
let moveLength = moveX - startX;
let res = _fixedValue(lastWidth + moveLength / sliderWidth);
_setProgress(res);
//记录当前位置
setLastWidth(res);
}
}
const _onmouseup=(e)=>
{
if (disabled)
{
return;
}
setShowTip(false);
setSliding(false);
}
document.documentElement.addEventListener('mousemove', _onmousemove);
document.documentElement.addEventListener('mouseup', _onmouseup);
return () => {
document.documentElement.removeEventListener('mousemove', _onmousemove);
document.documentElement.removeEventListener('mouseup', _onmouseup);
// document.documentElement.removeEventListener('mouseup', _onmouseup);
}
}, [sliding,startX,sliderWidth])
//圆圈按钮点击事件
const _onmousedown = useCallback((e) => {
if (!disabled)
{
setStartX(e.pageX);
setSliding(true);
setShowTip(true);
}
}, [])
//轨道点击设置位置
const _setSlider = useCallback((e) => {
if (!disabled) {
//当点击圆圈时,触发的源头变成了圆圈e.nativeEvent.offsetX为在圆圈是的坐标
if (e.target.dataset.target === 'circle')
{
//如果需要点击圆圈也可以微调
// 比较e.nativeEvent.offsetX和圆圈一半,小则减大则加
// setLastWidth((v)=> v+(e.nativeEvent.offsetX / sliderWidth);
return;
}
setShowTip(true);
setLastWidth(e.nativeEvent.offsetX / sliderWidth);
_setProgress(e.nativeEvent.offsetX / sliderWidth)
}
}, [sliderWidth])
//当前值(计算属性)
const _value = useMemo(() => {
timer && clearTimeout(timer);
let timerCon = setTimeout(() => {
//防抖传递参数给父组件
props.onChange(Math.floor(lastWidth * 100));
}, 200)
setTimer(timerCon);
return Math.floor(lastWidth*100)
},[lastWidth])
//设置进度方法
const _setProgress = useCallback((res) => {
sliderf.current.style.setProperty('--circleLeft', res*100 + '%');
sliderf.current.style.setProperty('--progressWidth', res * 100 + '%');
}, []);
//边界处理
const _fixedValue = useCallback((val) => {
return Math.max(0,Math.min(1,val));
}, [])
return(
<div ref={sliderf} onMouseDown={_setSlider} className={`jf-slider-container ${disabled&&'jf-slider-container-disabled'}`}>
<div className={`jf-slider-progress ${disabled&&'jf-slider-progress-disabled'}`}></div>
<div data-target='circle' onMouseDown={_onmousedown} className={`jf-slider-circle ${!disabled ? 'jf-slider-circle-notDisabled' : 'jf-slider-circle-disabled'}`}></div>
<div className={` jf-slider-tooltip ${showTip&&'jf-slider-tooltip-show'}`}>{ _value}</div>
</div>
)
}
App.propTypes = {
disabled: PropTypes.bool,
onChange: PropTypes.func,
value: PropTypes.number,
sliderHeight:PropTypes.string,
sliderWdith: PropTypes.string,
sliderColor: PropTypes.string,
sliderBorderRadius:PropTypes.string,
progressColor:PropTypes.string,
circleSize:PropTypes.string,
circleColor:PropTypes.string,
circleBorderColor:PropTypes.string,
tooltipSize: PropTypes.string,
tooltipColor:PropTypes.string,
tooltipFontColor: PropTypes.string,
tooltipFontSize: PropTypes.string,
tooltipBorderRadius:PropTypes.string,
}
App.defaultProps = {
value:0
}
export default React.memo(App);
slider.css:
.jf-slider-container {
height: var(--sliderHeight);
width: var(--sliderWidth);
background-color: var(--sliderColor);
position: relative;
margin-left: 40px;
border-radius: var(--sliderBorderRadius);
cursor: pointer;
--sliderHeight: 6px;
--sliderWidth: 500px;
--sliderColor: #e4e7ed;
--sliderBorderRadius: 5px;
--progressWidth: 0px;
--progressColor: #409eff;
--circleSize: 20px;
--circleLeft: 0px;
--circleColor: white;
--circleBorderColor: #409eff;
--tooltipSize: 35px;
--tooltipColor: black;
--tooltipFontColor: white;
--tooltipFontSize: 16px;
--tooltipBorderRadius: 5px;
}
div .jf-slider-container-disabled {
cursor: default;
}
.jf-slider-progress {
width: var(--progressWidth);
height: 100%;
background-color: var(--progressColor);
border-radius: var(--sliderBorderRadius);
}
.jf-slider-progress-disabled {
background-color: #c0c4cc;
}
.jf-slider-circle {
position: absolute;
box-sizing: border-box;
left: var(--circleLeft);
height: var(--circleSize);
width: var(--circleSize);
border-radius: 100%;
transform: translateX(-50%);
margin-top: calc(-1 * ((var(--circleSize) / 2) + (var(--sliderHeight) / 2)));
border: solid 2px var(--circleBorderColor);
transition: transform 0.25s;
cursor: pointer;
background-color: var(--circleColor);
}
.jf-slider-circle-notDisabled:hover {
transform: translateX(-50%) scale(1.25);
}
div .jf-slider-circle-disabled {
cursor: not-allowed;
border-color: #c0c4cc;
}
.jf-slider-tooltip {
position: absolute;
box-sizing: border-box;
left: var(--circleLeft);
top: 0;
height: var(--tooltipSize);
width: var(--tooltipSize);
border-radius: var(--tooltipBorderRadius);
transform: translateX(-50%);
margin-top: calc(-1 * (var(--tooltipSize) + (var(--circleSize) / 2)));
z-index: 10;
cursor: pointer;
color: var(--tooltipFontColor);
background-color: var(--tooltipColor);
text-align: center;
line-height: var(--tooltipSize);
font-size: var(--tooltipFontSize);
transition: transform 0.5s, opacity 2s 1s;
opacity: 0;
}
.jf-slider-circle:hover + .jf-slider-tooltip {
transition: transform 0.5s, opacity 0.5s;
opacity: 1;
}
.jf-slider-tooltip-show {
transition: transform 0.5s, opacity 0.5s;
opacity: 1;
}