[유데미x스나이퍼팩토리] 10주 완성 프로젝트 캠프 - JavaScript 과제

강경서·2023년 6월 18일
0
post-thumbnail

💻 (구)세이프홈즈 랜딩페이지 반응형 퍼블리싱 - JavaScript

(구)세이프홈즈 랜딩페이지 반응형 퍼블리싱

  • 리뷰 슬라이더 제작
  • 바닐라 자바스크립트로만 작성

(구)세이프홈즈 랜딩페이지 하단 리뷰 섹션의 슬라이더를 제작하였습니다. JavaScript를 작성하기 앞서 리뷰 섹션의 CSS 변경이 필요해 보였습니다. 기존의 CSS로는 슬라이더 페이지의 갯수가 홀수, 짝수에 따라 디자인이 의도되지 않은 형태로 변경되었습니다. 이를 변경해줄 필요가 보였지만, JavaScript를 통해 전부 통제하기 위해 CSS변경 없이 그대로 진행하였습니다.


HTML

Github 링크

  • index.html
 <section class="introduce review slider">
        <h3>세이프홈즈, <strong>없었으면 큰일 날 뻔했어요!</strong></h3>
        <div class="slider__wrapper">
          <ul class="review__container slider__wrapper__container">
            <li class="review__container__box">
              <header>
                <img src="./images/avatar_2.png" alt="avatar" />
                <div class="review__container__box__profile">
                  <span>홍길동님</span>
                  <span>전/월세 보증금 지킴이 리포트</span>
                </div>
              </header>
              <p>
                공인중개사가 계약을 너무 서두르는 느낌이 들어 불안했는데,
                세이프홈즈를 통해서 리포트를 받아보니 아니나 다를까 위험요소가
                너무 큰 매물이었습니다. 근저당이 너무 높게 잡혀있었고, 보증보험
                적합 대상도 아니라 경매로 넘어가게 되면 보증금을 돌려받기 힘들
                것 같아 해당 매물 계약을 포기하고, 더 안전한 매물을 찾아서
                계약할 수 있었습니다.
              </p>
            </li>
            <li class="review__container__box">
              <header>
                <img src="./images/avatar_2.png" alt="avatar" />
                <div class="review__container__box__profile">
                  <span>홍길동님</span>
                  <span>전/월세 보증금 지킴이 리포트</span>
                </div>
              </header>
              <p>
                공인중개사가 계약을 너무 서두르는 느낌이 들어 불안했는데,
                세이프홈즈를 통해서 리포트를 받아보니 아니나 다를까 위험요소가
                너무 큰 매물이었습니다. 근저당이 너무 높게 잡혀있었고, 보증보험
                적합 대상도 아니라 경매로 넘어가게 되면 보증금을 돌려받기 힘들
                것 같아 해당 매물 계약을 포기하고, 더 안전한 매물을 찾아서
                계약할 수 있었습니다.
              </p>
            </li>
            <li class="review__container__box">
              <header>
                <img src="./images/avatar_2.png" alt="avatar" />
                <div class="review__container__box__profile">
                  <span>홍길동님</span>
                  <span>전/월세 보증금 지킴이 리포트</span>
                </div>
              </header>
              <p>
                공인중개사가 계약을 너무 서두르는 느낌이 들어 불안했는데,
                세이프홈즈를 통해서 리포트를 받아보니 아니나 다를까 위험요소가
                너무 큰 매물이었습니다. 근저당이 너무 높게 잡혀있었고, 보증보험
                적합 대상도 아니라 경매로 넘어가게 되면 보증금을 돌려받기 힘들
                것 같아 해당 매물 계약을 포기하고, 더 안전한 매물을 찾아서
                계약할 수 있었습니다.
              </p>
            </li>
          </ul>
          <div class="slider__wrapper__controller">
            <div class="slider__wrapper__controller__container">
              <button class="slider__wrapper__controller__left"></button>
              <button class="slider__wrapper__controller__right"></button>
            </div>
            <div class="slider__wrapper__controller__pagination"></div>
          </div>
        </div>
        <a href="#" class="section-link">후기 진위여부 파악하러 가기 〉</a>
      </section>

슬라이더에 필요한 controller와 pagination을 위한 태그를 추가하였습니다. 이때 controller와 pagination, 그리고 슬라이더를 모두 감싸주는 태그를 만들어 주었습니다. 이는 controller와 pagination를 슬라이더가 hover 상태 일 때만 display를 보여주고, positon의 absolute속성을 활용하여 위치를 조정하기 위해서입니다.


CSS

Github 링크

  • css/styles.css
.slider__wrapper {
  position: relative;
}

.slider__wrapper:hover .slider__wrapper__controller {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 160px;
}

main .review__container {
  display: flex;
  gap: 45px;
  transition: all 1s ease-in-out;
}

.slider__wrapper__controller {
  width: 100%;
  display: none;
  top: 200px;
  position: absolute;
}

.slider__wrapper__controller__container {
  display: flex;
  gap: 900px;
}

.slider__wrapper__controller button {
  width: 50px;
  height: 50px;
  cursor: pointer;
  font-size: 24px;
  text-align: center;
  line-height: 32px;
  border: none;
  border-radius: 50%;
  color: #fff;
  background-color: var(--green);
  box-shadow: rgba(0, 0, 0, 0.3) 2px 2px 5px 1px;
}

.slider__wrapper__controller button:hover {
  background-color: #0d5140;
}

.slider__wrapper__controller__pagination {
  display: flex;
  gap: 5px;
}

.pagination {
  display: inline-block;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background-color: rgba(0, 0, 0, 0.3);
}

.checked {
  background-color: rgba(0, 0, 0, 0.6);
}

슬라이더의 기본 디자인과 hover 상태일때만 display를 보여지도록 CSS를 작성했습니다. 특히 위에서 이미 작성된 중복된 속성을 고려하여 CSS우선 적용 순위를 고려하며 작성했습니다.


  • css/responsive.css
@media screen and (max-width: 1024px) {
  .slider__wrapper__controller__container {
    gap: 700px;
  }
}

@media screen and (max-width: 768px) {
  .slider__wrapper__controller__container {
    gap: 500px;
  }
}

@media screen and (max-width: 480px) {
  .slider__wrapper {
    overflow: scroll;
  }

  .slider__wrapper:hover .slider__wrapper__controller {
    display: none;
  }
}

반응형 디자인 또한 변경해줍니다. 특히 480px이하의 모바일 환경에서는 기존의 슬라이더 대신 overflow: scroll 속성을 사용하여 모바일 환경에 맞는 슬라이더로 변경했습니다.


CSS 결과

슬라이더가 hover 상태 일때만 controller와 pagination이 보여집니다.


JavaScript

Github 링크

JavaScript를 작성하기 앞서 필요 기능을 정리해보았습니다.

  1. 가운데 정렬로 디자인된 슬라이더를 첫번째 순서로 위치를 변경한다.
  2. pagination을 슬라이드 갯수만큼 생성하여 표시한다.
  3. 슬라이더의 갯수가 짝수 및 홀수에 따라 다른 이벤트를 실행시킨다.
  4. 슬라이더 이동시 pagination또한 순서에 맞게 생삭이 변경된다.

1, 3번과 같은 경우는 초기의 CSS 디자인을 수정한다면 슬라이드의 갯수와 상관없이 가능하지만 JavaScript만으로 완전히 통제하기 위해 짝수, 홀수 구분지어 코드를 작성했습니다.

  • js/script.js
const sliderContainer = document.querySelector(".slider__wrapper__container");
const sliderController = document.querySelector(".slider__wrapper__controller");
const leftBtn = sliderController.querySelector(
  ".slider__wrapper__controller__left"
);
const rightBtn = sliderController.querySelector(
  ".slider__wrapper__controller__right"
);
const paginationContainer = sliderController.querySelector(
  ".slider__wrapper__controller__pagination"
);

let order = 1;
const listLength = sliderContainer.childElementCount;
const center = Math.round(listLength / 2);

필요한 HTML element와 슬라이더에 필요한 변수와 상수를 선언합니다. 슬라이더의 순서를 통제할 order, 슬라이더의 갯수에 해당하는 listLength 그라고 슬라이더의 중앙값에 해당하는 center를 선언하였습니다. center의 필요성은 첫 랜딩페이지에서 슬라이더가 가운데 정렬로 디자인이 되어있어 이를 통제하기 위해 선언하였습니다.


const onStart = () => {
  order = 1;
  if (listLength % 2 === 0) {
    sliderContainer.style.transform = `translateX(${
      (Math.floor(listLength / 2) / listLength) * 100 - (1 / listLength) * 50
    }%)`;
  } else {
    sliderContainer.style.transform = `translateX(${
      (Math.floor(listLength / 2) / listLength) * 100
    }%)`;
  }
};

onStart();

슬라이더의 순서를 첫번째로 이동시키기 위한 onStart 함수를 작성하고 이를 바로 선언합니다.
이때 슬라이드의 갯수가 짝수 및 홀수에 따라 조건문을 활용해 구분하였습니다.


const createPagination = () => {
  for (let i = 0; i < listLength; i++) {
    const pagination = document.createElement("span");
    pagination.classList.add("pagination");
    pagination.id = i + 1;
    paginationContainer.append(pagination);
    if (i === 0) {
      pagination.classList.add("checked");
    }
  }
};

createPagination()

슬라이더의 갯수만큼 pagination을 생성해주는 함수를 작성하고 이를 바로 선언합니다. CSS에 미리 만들어둔 class를 이용하여 선택된 pagination의 색상을 변경해주었습니다.


const checkPagination = (order, onRight) => {
  const paginations = paginationContainer.querySelectorAll(".pagination");
  paginations.forEach((pagination) => {
    if (+pagination.id === order) {
      pagination.classList.remove("checked");
    }
  });
  if (order === listLength && onRight) {
    paginations.forEach((pagination) => {
      if (+pagination.id === 1) {
        pagination.classList.add("checked");
      }
    });
  } else if (order === 1 && !onRight) {
    paginations.forEach((pagination) => {
      if (+pagination.id === listLength) {
        pagination.classList.add("checked");
      }
    });
  } else if (onRight) {
    paginations.forEach((pagination) => {
      if (+pagination.id === order + 1) {
        pagination.classList.add("checked");
      }
    });
  } else {
    paginations.forEach((pagination) => {
      if (+pagination.id === order - 1) {
        pagination.classList.add("checked");
      }
    });
  }
};

const onOddLeft = () => {
  checkPagination(order, false);
  order = order - 1;
  if (order === 0) {
    onEnd();
  } else if (order === 1) {
    onStart();
  } else if (order < center) {
    sliderContainer.style.transform = `translateX(${
      (center - order) * (1 / listLength) * 100
    }%)`;
  } else if (order > center) {
    sliderContainer.style.transform = `translateX(-${
      ((order - center) / listLength) * 100
    }%)`;
  } else {
    sliderContainer.style.transform = `translateX(0%)`;
  }
};

const onOddRight = () => {
  checkPagination(order, true);
  order = order + 1;
  if (order > listLength) {
    onStart();
  } else if (order < center) {
    sliderContainer.style.transform = `translateX(${
      (center - order) * (1 / listLength) * 100
    }%)`;
  } else if (order > center) {
    sliderContainer.style.transform = `translateX(-${
      ((order - center) / listLength) * 100
    }%)`;
  } else {
    sliderContainer.style.transform = `translateX(0%)`;
  }
};

const onEvenLeft = () => {
  order = order - 1;
  if (order === 0) {
    onEnd();
  } else if (order === 1) {
    onStart();
  } else if (order < center) {
    sliderContainer.style.transform = `translateX(${
      (center - order) * (1 / listLength) * 100 + (1 / listLength) * 50
    }%)`;
  } else if (order > center) {
    sliderContainer.style.transform = `translateX(-${
      ((order - center) / listLength) * 100 - (1 / listLength) * 50
    }%)`;
  } else {
    sliderContainer.style.transform = `translateX(${(1 / listLength) * 50}%)`;
  }
};

const onEvenRight = () => {
  order = order + 1;
  if (order > listLength) {
    onStart();
  } else if (order < center) {
    sliderContainer.style.transform = `translateX(${
      (center - order) * (1 / listLength) * 100 + (1 / listLength) * 50
    }%)`;
  } else if (order > center) {
    sliderContainer.style.transform = `translateX(-${
      ((order - center) / listLength) * 100 - (1 / listLength) * 50
    }%)`;
  } else {
    sliderContainer.style.transform = `translateX(${(1 / listLength) * 50}%)`;
  }
};

const onStart = () => {
  order = 1;
  if (listLength % 2 === 0) {
    sliderContainer.style.transform = `translateX(${
      (Math.floor(listLength / 2) / listLength) * 100 - (1 / listLength) * 50
    }%)`;
  } else {
    sliderContainer.style.transform = `translateX(${
      (Math.floor(listLength / 2) / listLength) * 100
    }%)`;
  }
};

const onEnd = () => {
  order = listLength;
  if (listLength % 2 === 0) {
    console.log(1);
    sliderContainer.style.transform = `translateX(-${
      (Math.floor(listLength / 2) / listLength) * 100 - (1 / listLength) * 50
    }%)`;
  } else {
    sliderContainer.style.transform = `translateX(-${
      (Math.floor(listLength / 2) / listLength) * 100
    }%)`;
  }
};

if (listLength % 2 === 0) {
  rightBtn.addEventListener("click", onEvenRight);
  leftBtn.addEventListener("click", onEvenLeft);
} else {
  rightBtn.addEventListener("click", onOddRight);
  leftBtn.addEventListener("click", onOddLeft);
}

order를 이용하여 슬라이더 순서를 통제하고 슬라이더를 transform 속성을 변경하여 이동시킵니다. 이때 order와 listLength를 이용하여 슬라이더를 "px"이아닌 "%"단위로 이동시켜 슬라이더의 갯수 및 크기의 변함에 상관없이 일정하게 슬라이더를 이동시켜줍니다. 또한 슬라이더가 이동할때마다 order를 통해 pagination를 순서에 맞게 색상을 변경시켜줍니다. 이는 pagination을 생성할때 id값을 미리 부여하여 가능합니다. 또한 슬라이더의 갯수가 짝수 및 홀수에 따라 controller 버튼에 다른 함수를 실행시킵니다.


JavaScript 결과

바닐라 자바스크립트로 슬라이더를 제작하였습니다.


📝 후기

프론트앤드의 역량은 JavaScript에서 전부 나온다고 생각했었습니다. 하지만 이번 과제에서 CSS 디자인의 차이로 JavaScript의 코드의 변화가 매우 크다고 느꼈습니다. 분명 JavaScript만으로도 충분히 통제가 가능하지만 CSS와 같이 통제한다면 더 효율적인 코딩이 가능할거라고 느꼈습니다.



본 후기는 유데미-스나이퍼팩토리 10주 완성 프로젝트캠프 학습 일지 후기로 작성 되었습니다.

#프로젝트캠프 #프로젝트캠프후기 #유데미 #스나이퍼팩토리 #웅진씽크빅 #인사이드아웃 #IT개발캠프 #개발자부트캠프 #리액트 #react #부트캠프 #리액트캠프

profile
기록하고 배우고 시도하고

0개의 댓글