ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SVG로 Progress-bar 만들기 with JS로 width 변경
    FrontEnd/JavsScript 2021. 3. 23. 01:28

    남은 시간을 진행바로 표시해줘야 하는 상황이 생겼다.

    CSS에 -webkit-progress-bar 라는 프로퍼티가 있다고 듣기는 했는데 아직 생소해서 이번에는 최대한 아는 걸로 만들어보기로 했다.

    시간이 주어지면 (ex 10초) 남은 시간만큼 타임바가 짧아지다가 사라지는 형태다.

    큰 아이디어는 이쪽에서 얻었다: ProgressBar 구현하는 3가지 방법

     

    먼저 직선 모양이 있어야하니 svg 태그를 추가한다. 이미지를 잡기 위해서 우선 기본값을 넣어주었다.

    (Move to 좌표(0, 0) -> Horizontal 가로 좌표(변수) -> Vertical 세로 좌표 10으로 이동 -> 거기서 0으로 되돌아와서 마저 채우기)

    <div class="wrapper progress-bar">
      <svg class="bar progres-bar">
        <path d="M 0 0 H 500 V 10 H 0" />
      </svg>
    </div>

     

    그리고 js를 구성한다. 먼저 상수처리. 셀렉터를 가져오고, 시간 제한을 정해준다.

    const SELECTOR = {
      PROGRESS_BAR: {
        WRAPPER: document.querySelector('.wrapper.progress-bar'),
        BAR: document.querySelector('.bar.progress-bar'),
        PATH: document.querySelector('.bar.progress-bar').firstElementChild,
      },
    };
    
    const DATA = {
      TIME_LIMIT: 10000,
      CURRENT_TIMER: null,
    }
    

    진행바를 처리할 변수를 하나 만들고, 처리할 넓이를 주면 path 값을 리턴하도록 메소드를 만든다.

    진행바 크기는 부모 요소의 크기에 따라 변해야 하기 때문에 offsetWidth를 가져왔다.

    remaining에는 남은 시간을 담을 예정이다.

    const progressBar = {
      getNewPath({ width }) {
        return `M 0 0 H ${width} V 10 H 0`;
      },
      get eachPoint() {
        return SELECTOR.PROGRESS_BAR.WRAPPER.offsetWidth / TIME_LIMIT;
      },
      remaining: 0,
    };

     

    타이머는 재귀함수로 만들어서, 남은 시간이 있는지 확인해보고 있으면 다시 실행하도록 했다.

    function reduceRemainingTime(delay) {
      if (progressBar.remaining > 0) {
        progressBar.remaining -= delay;
        return true;
      }
      progressBar.remaining = 0;
      // handleTimeOut(); 종료 후 로직은 이쪽에서 처리하면 된다.
      return false;
    }
    
    function checkRemainingTime(delay = 10) {
      const hasTime = reduceRemainingTime(delay);
      updateProgressBar();
      if (hasTime) {
        CURRENT_TIMER = setTimeout(checkRemainingTime, delay);
      }
    }
    
    function setTimer(delay = 1000) {
      progressBar.remaining = delay;
      CURRENT_TIMER = setTimeout(checkRemainingTime);
    }
    

     

    checkRemainingTime에서 실행할 updateProgressBar() 함수는 이렇게 구성되어 있다.

    <path> 태그의 'd' 속성에 위의 progressBar.getNewPath 메소드에서 구한 Width 값을 변경해서 넣어준다.

    Width 값은, 전체 Width에서 남은 시간/전체 시간의 비율을 점유한다.

    function changeSVGPathWidth(newPath) {
      SELECTOR.PROGRESS_BAR.PATH.setAttribute('d', newPath);
    }
    
    function convertWidth(width) {
      return progressBar.eachPoint * width;
    }
    
    function updateProgressBar() {
      const width = progressBar.remaining;
      const convertedWidth = convertWidth(width);
      const newPath = progressBar.getNewPath({
        width: convertedWidth,
      });
      changeSVGPathWidth(newPath);
    }
    

     

     

    CSS는 이렇다.

    위에서 사용한 Width가 부모인 div태그(.wrpper) 기준이기 때문에 자식들은 꼭 width: 100%를 가져야 하고,

    .wrapper에는 padding이 있으면 안 된다.

    그렇지 않으면 overflow된 부분만큼은 진행바에 반영이 안 되기 때문에, 진행바가 overflow 영역에 들어가는 동안은 진행바가 갱신되지 않는 것처럼 보인다.

    .wrapper.progress-bar {
      margin: 50px;
    }
    
    .bar-box.progress-bar {
      width: 100%;
    }
    
    .bar.progress-bar {
      width: 100%;
      fill: rgb(177, 170, 226);
    }
    

     

    svg 태그의 너비를 바로 반영하고 싶다면 아래처럼 접근해서 처리할 수도 있다.

    SELECTOR.PROGRESS_BAR.BAR_BOX.width.animVal.value

    위의 prograssBar 객체를 정의했던 부분 중에서 offsetWidth 값 대신에 width ~ 를 넣어주면 된다.

    const progressBar = {
      getNewPath({ width }) {
        return `M 0 0 H ${width} V 10 H 0`;
      },
      get eachPoint() {
        return SELECTOR.PROGRESS_BAR.BAR_BOX.width.animVal.value / TIME_LIMIT;
      },
      remaining: 0,
    };

    댓글

Designed by Tistory.