React 中的双面输入滑块

Double sided input slider in React

我在下面的更新中链接的沙箱中有一个几乎可以工作的解决方案,你应该从这个开始。


我有一个没有使用 React 创建的(单击“运行 代码片段”)

var thumbsize = 14;

function draw(slider,splitvalue) {

    /* set function vars */
    var min = slider.querySelector('.min');
    var max = slider.querySelector('.max');
    var thumbsize = parseInt(slider.getAttribute('data-thumbsize'));
    var rangewidth = parseInt(slider.getAttribute('data-rangewidth'));
    var rangemin = parseInt(slider.getAttribute('data-rangemin'));
    var rangemax = parseInt(slider.getAttribute('data-rangemax'));

    /* set min and max attributes */
    min.setAttribute('max',splitvalue);
    max.setAttribute('min',splitvalue);

    /* set css */
    min.style.width = parseInt(thumbsize + ((splitvalue - rangemin)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
    max.style.width = parseInt(thumbsize + ((rangemax - splitvalue)/(rangemax - rangemin))*(rangewidth - (2*thumbsize)))+'px';
    min.style.left = '0px';
    max.style.left = parseInt(min.style.width)+'px';
    
    /* correct for 1 off at the end */
    if(max.value>(rangemax - 1)) max.setAttribute('data-value',rangemax);

    /* write value and labels */
    max.value = max.getAttribute('data-value'); 
    min.value = min.getAttribute('data-value');

}

function init(slider) {
    /* set function vars */
    var min = slider.querySelector('.min');
    var max = slider.querySelector('.max');
    var rangemin = parseInt(min.getAttribute('min'));
    var rangemax = parseInt(max.getAttribute('max'));
    var avgvalue = (rangemin + rangemax)/2;

    /* set data-values */
    min.setAttribute('data-value',rangemin);
    max.setAttribute('data-value',rangemax);
    
    /* set data vars */
    slider.setAttribute('data-rangemin',rangemin); 
    slider.setAttribute('data-rangemax',rangemax); 
    slider.setAttribute('data-thumbsize',thumbsize); 
    slider.setAttribute('data-rangewidth',slider.offsetWidth);

    /* draw */
    draw(slider,avgvalue);

    /* events */
    min.addEventListener("input", function() {update(min);});
    max.addEventListener("input", function() {update(max);});
}

function update(el){
    /* set function vars */
    var slider = el.parentElement;
    var min = slider.querySelector('#min');
    var max = slider.querySelector('#max');
    var minvalue = Math.floor(min.value);
    var maxvalue = Math.floor(max.value);
    
    /* set inactive values before draw */
    min.setAttribute('data-value',minvalue);
    max.setAttribute('data-value',maxvalue);

    var avgvalue = (minvalue + maxvalue)/2;

    /* draw */
    draw(slider,avgvalue);
}

var sliders = document.querySelectorAll('.min-max-slider');
sliders.forEach( function(slider) {
    init(slider);
});
.min-max-slider {
  position: relative;
  width: 200px;
  text-align: center;
  margin-bottom: 50px;
 }

.min-max-slider > label {
  display: none;
 }
 
.min-max-slider > input {
  cursor: pointer;
  position: absolute;
}


/* webkit specific styling */
.min-max-slider > input {
  -webkit-appearance: none;
  outline: none!important;
  background: transparent;
  background-image: linear-gradient(to bottom, transparent 0%, transparent 30%, silver 30%, silver 60%, transparent 60%, transparent 100%);
}

.min-max-slider > input::-webkit-slider-thumb {
  -webkit-appearance: none; /* Override default look */
  appearance: none;
  width: 14px; /* Set a specific slider handle width */
  height: 14px; /* Slider handle height */
  background: #eee; /* Green background */
  cursor: pointer; /* Cursor on hover */
  border: 1px solid gray;
  border-radius: 100%;
}

.min-max-slider > input::-webkit-slider-runnable-track {cursor: pointer;}
<div class="min-max-slider" data-legendnum="2">
    <label for="min">Minimum price</label>
    <input id="min" class="min" name="min" type="range" step="1" min="0" max="3000" />
    <label for="max">Maximum price</label>
    <input id="max" class="max" name="max" type="range" step="1" min="0" max="3000" />
</div>

我尝试使用 React 创建相同类型的组件。但是每个 thumbgrip 只能滑动到中点,与非反应示例一样,较小的滑块不能高于较高的滑块,反之亦然。

我还想要一些方法,使超出拇指手柄范围的滑块颜色更深。

var thumbsize = 14;

const Slider = ({min, max}) => {
    const avg = (min + max)/2;
  const width = 300
  const minWidth = thumbsize + ((avg - min)/(max - min))*(width - (2*thumbsize))
  const styles={
      min:{
        width: minWidth,
        left: 0
      },
      max:{
        width: thumbsize + ((max - avg)/(max - min))*(width - (2*thumbsize)),
        left: minWidth
      }
  }
  
    return (
    <div 
      class="min-max-slider"
      data-legendnum="2"
      data-rangemin={min}
      data-rangemax={max}
      data-thumbsize={thumbsize}
      data-rangewidth={width}
    >
      <label htmlFor="min">Minimum price</label>
      <input 
        id="min"
        className="min"
        style={styles.min}
        name="min"
        type="range"
        step="1"
        min={min}
        max={avg}
        data-value={min}
       />
      <label htmlFor="max">Maximum price</label>
      <input
        id="max"
        className="max"
        style={styles.max}
        name="max"
        type="range"
        step="1"
        min={avg}
        max={max}
        data-value={max}
       />
    </div>
  )
}

ReactDOM.render(<Slider min={300} max={3000} />, document.querySelector("#root"))
 



.min-max-slider {
  position: relative;
  width: 200px;
  text-align: center;
  margin-bottom: 50px;
 }

.min-max-slider > label {
  display: none;
 }
 
.min-max-slider > input {
  cursor: pointer;
  position: absolute;
}


/* webkit specific styling */
.min-max-slider > input {
  -webkit-appearance: none;
  outline: none!important;
  background: transparent;
  background-image: linear-gradient(to bottom, transparent 0%, transparent 30%, silver 30%, silver 60%, transparent 60%, transparent 100%);
}

.min-max-slider > input::-webkit-slider-thumb {
  -webkit-appearance: none; /* Override default look */
  appearance: none;
  width: 14px; /* Set a specific slider handle width */
  height: 14px; /* Slider handle height */
  background: #eee; /* Green background */
  cursor: pointer; /* Cursor on hover */
  border: 1px solid gray;
  border-radius: 100%;
}

.min-max-slider > input::-webkit-slider-runnable-track {cursor: pointer;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>


更新

使用下面的 lissetdm 解决方案,我已经能够创建一个双面滑块,您可以在 this sandbox 上查看它,但它仍然存在一些问题。

  1. 橙色看起来并不总是与灰色重叠

有时,出乎意料,这种差异非常明显

  1. 当两个拇指块都被拖到边缘时,一个将另一个向下推

  1. 我似乎无法删除这个轮廓,它并不总是出现


我已经在 firefox

中测试过了

问题是 minmax 是字符串,所以计算 avg 是不正确的,但是通过使用 parseInt 你会得到预期的整数。 here 我创建了一个沙箱,但我不得不将 width 的值从 30 更改为 300 以便为点制作足够的 space。

随着 React 的工作,您需要将 avg 移动到组件的状态,并在输入 onChange 事件期间更新它的值:

const Slider = ({ min, max }) => {
  const [avg, setAvg] = useState((min + max) / 2);
  const [minVal, setMinVal] = useState(avg);
  const [maxVal, setMaxVal] = useState(avg);

  const width = 300;
  const minWidth =
    thumbsize + ((avg - min) / (max - min)) * (width - 2 * thumbsize);
  const styles = {
    min: {
      width: minWidth,
      left: 0
    },
    max: {
      width: thumbsize + ((max - avg) / (max - min)) * (width - 2 * thumbsize),
      left: minWidth
    }
  };

  useEffect(() => {
    setAvg((maxVal + minVal) / 2);
  }, [minVal, maxVal]);


  return (
    <div
      className="min-max-slider"
      data-legendnum="2"
      data-rangemin={min}
      data-rangemax={max}
      data-thumbsize={thumbsize}
      data-rangewidth={width}
    >
      <label htmlFor="min">Minimum price</label>
      <input
        id="min"
        className="min"
        style={styles.min}
        name="min"
        type="range"
        step="1"
        min={min}
        max={avg}
        value={minVal}
        onChange={({ target }) => setMinVal(Number(target.value))}
      />
      <label htmlFor="max">Maximum price</label>
      <input
        id="max"
        className="max"
        style={styles.max}
        name="max"
        type="range"
        step="1"
        min={avg}
        max={max}
        value={maxVal}
        onChange={({ target }) => setMaxVal(Number(target.value))}
      />
    </div>
  );
};

已更新

I can't seem to remove this outline, which doesn't always appear

出现此行是因为浏览器检测到输入无效。要禁用此效果,请使用:

input[type="range"]:invalid {
  box-shadow: none;
}

The orange doesn't always look like it overlaps the gray

When both thumb pieces are dragged to the edges, the one pushes the other down

我无法重现这种情况,但我建议您使用最小和最大位置来控制背景颜色:

CSS:

input[type="range"] {
  --minRangePercent: 0%;
  --maxRangePercent: 0%;
  height: .4rem;
}

.min-max-slider > input.min {
  background-image: linear-gradient(
    to right,
    silver 0%,
    silver var(--minRangePercent),
    orange var(--minRangePercent),
    orange 100%
  );
}

.min-max-slider > input.max {
  background-image: linear-gradient(
    to right,
    orange 0%,
    orange var(--maxRangePercent),
    silver var(--maxRangePercent),
    silver 100%
  );
}

JS:

const minPercent = ((minVal - min) / (avg - min)) * 100;
const maxPercent = ((maxVal - avg) / (max - avg)) * 100;
const styles = {
    min: {
      width: minWidth,
      left: 0,
      "--minRangePercent": `${minPercent}%`
    },
    max: {
      width: thumbsize + ((max - avg) / (max - min)) * (width - 2 * thumbsize),
      left: minWidth,
      "--maxRangePercent": `${maxPercent}%`
    }
 };

Working example