양방향 HTML input range 만들기

유제·2020년 11월 26일
12

연구

목록 보기
1/1
post-thumbnail

개인 프로젝트를 진행하면서 위와 같이 양쪽에서 조절이 가능한 input을 만들고 싶은 상황이 생긴 김에 기록까지 남깁니다.

양방향 range 만들기의 핵심은 JS를 이용하여 2개의 range를 각각 div slider thumb에 연결을 하는 것입니다. div slider thumb은 위의 그림에서 검은 동그라미를 의미합니다. 검은 동그라미는 div를 이용해 만들며, css선택자중에 ::-webkit-slider-thumb이라는 가상요소 선택자가 있어서 div slider thumb이라고 임의로 이름을 붙였습니다.

HTML 구조


<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Document</title>
  </head>
  <body>
    <form class="box">
      <div class="slider">
        <input type="range" id="input-left" min="1" max="100" value="1" />
        <input type="range" id="input-right" min="1" max="100" value="100" />
        <div class="track">
          <div class="range"></div>
          <div class="thumb left"></div>
          <div class="thumb right"></div>
        </div>
      </div>
    </form>
    <script src="main.js"></script>
  </body>
</html>

  • form 태그 : slider를 단독으로 사용하지 않는 상황이 많다고 생각해서 form 태그로 감쌌습니다.

  • slider div: 2개의 input과 track div를 그룹화해주는 역할을 합니다.

  • track div: 양방향 input range의 실질적인 디자인에 영향을 미치는 element입니다. track div는 처음에 나온 그림에서 길다란 연한 회색 요소의 역할을 합니다.

  • range div: 두 div slider thumb 사이의 진한 회색 요소의 역할을 합니다.

  • thumb div: 검은 동그라미 역할을 하며, input의 값이 바뀔 때마다 위치가 바뀝니다.

1차 CSS


html,
body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

* {
  box-sizing: border-box;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
}

.box {
  width: 500px;
  height: 300px;
  background-color: #ecf0f1;
}

.slider {
  width: 100%;
  height: 100%;
  /* 후에 padding: 1.5rem 2rem 으로 수정합니다. 참고바랍니다. */
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
  position: relative;
}

input {
  width: calc(100% - 2rem);
  top: 3rem;
  left: 1rem;
  position: absolute;
  border: none;
}

input:first-child {
  top: 1rem;
}

.track {
  position: relative;
  width: 100%;
  height: 0.5rem;
  margin-top: 5rem;
  background-color: #bdc3c7;
  border-radius: 0.5rem;
}

.range {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: #2c3e50;
  border-radius: 0.5rem;
}

.thumb {
  position: absolute;
  top: 0;
  transform: translateY(-0.25rem);
  width: 1rem;
  height: 1rem;
  background-color: black;
  border-radius: 50%;
}

.left {
  left: 0;
}

.right {
  right: 0;
}


JS

const inputLeft = document.getElementById("input-left");
const inputRight = document.getElementById("input-right");

const thumbLeft = document.querySelector(".thumb.left");
const thumbRight = document.querySelector(".thumb.right");

const range = document.querySelector(".range");

const setLeftValue = e => {
  const _this = e.target;
  const { value, min, max } = _this;

  if (+inputRight.value - +value < 10) {
    _this.value = +inputRight.value - 10;
  }

  const percent = ((+_this.value - +min) / (+max - +min)) * 100;

  thumbLeft.style.left = `${percent}%`;
  range.style.left = `${percent}%`;
};

const setRightValue = e => {
  const _this = e.target;
  const { value, min, max } = _this;

  if (+value - +inputLeft.value < 10) {
    _this.value = +inputLeft.value + 10;
  }

  const percent = ((+_this.value - +min) / (+max - +min)) * 100;

  thumbRight.style.right = `${100 - percent}%`;
  range.style.right = `${100 - percent}%`;
};

if (inputLeft && inputRight) {
  inputLeft.addEventListener("input", setLeftValue);
  inputRight.addEventListener("input", setRightValue);
}

위 JS코드를 적용시키면 대략 아래처럼 작동합니다.

div로 만든 track의 너비가 input보다 좁다는 걸 눈치채신 분들이 있을지 모르겠네요.
제가 저렇게 한 이유는 input의 너비와 track의 너비를 같게 하면 input의 thumb과 div thumb의 위치가 일치하지 않습니다. div thumb이 조금 더 많이 움직입니다. 이게 나중에 문제가 되기 때문에 어쩔 수 없이 track의 너비를 조금 좁히고, 두 input의 value의 차이를 10보단 크도록 설정했습니다.

금방 언급한 문제를 제외하면 이제 해결해야되는 중요한 문제는 하나가 남게됩니다. 바로 div slider thumb를 움직일 때 진짜 input의 thumb이 움직이도록 하는 것입니다. 그래야 input의 값을 사용할 수 있기 때문이죠.

이 문제는 CSS만으로 해결이 가능합니다. 기본적인 개념은 track 위에 두 input을 겹쳐놓고 input의 opacity를 0으로 설정하는 것입니다. 기본적인 개념에다 추가적인 작업을 하면 모든 게 끝이 납니다.

2차 CSS

input 태그의 css를 아래에 있는 css로 대체하고, ::-webkit-slider-thumb 가상요소 선택자에 대한 css를 추가하면 끝이 납니다.


input {
  width: calc(100% - 2rem);
  top: 1rem;
  left: 1rem;
  position: absolute;
  border: none;
  pointer-events: none;
  z-index: 10;
  appearance: none;
  opacity: 0;
}

input::-webkit-slider-thumb {
  pointer-events: all;
  /* appearance, background-color는 지워도 됨 */
  appearance:none;
  background-color: red;
  width: 2.5rem;
  height: 1.5rem;
}

이렇게 작동합니다.

2차 CSS까지 적용한 후 input의 opacity를 0.5로 조정하면 아래처럼 보입니다.

이 gif를 보면 div thumb과 input thumb의 위치가 어느정도 일치합니다. 이것은 사실 js코드 아래 있는 gif에서 나온 track div의 너비를 조금 더 줄였습니다. 너비를 줄이지 않고 해봤더니 오른쪽 input을 왼쪽으로 쭉 드래그했을 때, div thumb의 절반 이상이 input thumb 바깥으로 나와버려서 사용자 입장에서는 굉장히 불편함이 느껴졌을 것입니다.

1개의 댓글

comment-user-thumbnail
2021년 4월 8일

안녕하세요 보고 배우고 있습니다!! 똑같이 따라햇는데 검은 선이 작동을 안하네요ㅠㅠ 왜그런걸까요

답글 달기