Двусторонний ползунок ввода в React

#javascript #reactjs #slider

#javascript #reactjs #слайдер

Вопрос:

У меня есть почти рабочее решение в изолированной среде, связанное с обновлением ниже, вы должны начать с этого.


У меня есть тот, который создан без использования 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. Но каждая ручка может скользить только до середины, где, как и в примере без реакции, меньший ползунок просто не может быть выше верхнего ползунка и наоборот.

Я также хотел бы каким-то образом сделать ползунок, который находится за пределами диапазона кнопок, более темного цвета.

 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, приведенное ниже, я смог создать двусторонний слайдер, который вы можете просмотреть в этой изолированной среде, но с ним все еще есть несколько проблем.

  1. Оранжевый не всегда выглядит так, как будто он перекрывает серый

введите описание изображения здесь

Иногда, непредсказуемо, эта разница очень заметна

введите описание изображения здесь
введите описание изображения здесь

  1. Когда обе части большого пальца перетаскиваются к краям, одна из них толкает другую вниз

введите описание изображения здесь

  1. Кажется, я не могу удалить этот контур, который не всегда появляется

введите описание изображения здесь


Я протестировал это в firefox

Комментарии:

1. В Google Chrome он имеет другой стиль. Должен ли он быть оранжевого цвета посередине и серого цвета по бокам?

Ответ №1:

Чтобы react работал, вам нужно переместить avg в состояние компонента и во время события input 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>
  );
};
 

ОБНОВЛЕНО

Кажется, я не могу удалить этот контур, который не всегда появляется

Эта строка появляется, потому что браузер обнаруживает, что ввод недопустим. Чтобы отключить этот эффект, используйте:

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

Оранжевый не всегда выглядит так, как будто он перекрывает серый

И

Когда обе части большого пальца перетаскиваются к краям, одна из них толкает другую вниз

Я не могу воспроизвести этот сценарий, но я рекомендую вам управлять цветом фона, используя минимальное и максимальное положение:

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}%`
    }
 };
 

Рабочий пример

Комментарии:

1. Я поместил ваш слайдер в изолированную среду кода , и он сложный, а также у обеих частей большого пальца все еще есть min и max

2. Да, я вижу, что он ведет себя по-разному в зависимости от браузера, я добавил значение свойства, чтобы избежать этого. смотрите: codesandbox.io/s/rough-silence-dt73v?file=/src/App.js

3. Я заметил еще одну ошибку, извините, когда вы растягиваете элементы большого пальца до их минимального и максимального значений, один из них становится неактивным

4. О, не волнуйтесь, идея в том, что он работает нормально, я обновил код, думаю, теперь он выглядит лучше, codesandbox.io/s/rough-silence-dt73v?file=/src/App.js

5. Идеальный. И код в этой изолированной среде соответствует коду в вашем ответе?

Ответ №2:

Проблема заключалась в том, что min and max являются строками, поэтому вычисление avg было неправильным, но при использовании parseInt вы получите целые числа, как и ожидалось. здесь я создал изолированную среду, но мне пришлось изменить значение width from 30 to 300 , чтобы освободить достаточно места для точек.

Комментарии:

1. Спасибо, я посмотрел на это, но была еще одна проблема, что каждый палец мог скользить только до середины. Если вы посмотрите на простой пример javascript, этого не произойдет