重构-滑块(拉动条)

实现:
	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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值