그래도 해야지. 어떡해

2023 portfolio 본문

개발일지/Projects

2023 portfolio

정원의쓸모 2023. 9. 18. 18:16

💼 작업기간 (약 3주)

• 디자인 1주

 코딩 2주


📌 주요 기능

1. 비주얼

   1-1. aos, vivus 라이브러리 사용

   1-2. svg 스크롤 이벤트

2. 프로젝트

   2-1. 아이템 필터

   2-2. 필터 버튼 클릭 시, 투명도 조절

   2-3. ( 앨범형 / 리스트형 ) 보기 방식 변경

   2-4. 모바일형 필터 버튼

3. 스와이퍼 적용

4. 반응형 레이아웃

5. 페이지 색상 변경 이벤트


1. 비주얼

1-1. 타이틀 : AOS, vivus 라이브러리를 이용해 모션 추가

 vivus.js
https://maxwellito.github.io/vivus-instant/

 

1-2.  svg 스크롤 이벤트

• 스크롤의 속도에 따라 배경에 있는 svg의 위치(transformY)를 바꾸기

- js

const $deco1 = document.querySelector(".deco-1");
const $deco2 = document.querySelector(".deco-2");

window.addEventListener("scroll", () => {
  const pos = window.scrollY;
  const speedFactor = 0.2;
  // 천천히 올라가는 이미지의 속도 조절

  // 이미지가 천천히 올라가도록 설정
  $deco1.style.transform = `translateY(-${pos * speedFactor}px)`;
  $deco2.style.transform = `translateY(-${pos * speedFactor}px)`;
});

 

2. 프로젝트

2-1. 아이템 필터

• "data-"를 이용해 데이터 속성값 지정해 두기

- html

<nav class="filter">
 <button class="active option_item" data-filter="all">All</button>
 <button class="option_item" data-filter="responsive">Responsive</button>
 <button class="option_item" data-filter="team">Team</button>
 <button class="option_item" data-filter="single">Single</button>
 <button class="option_item" data-filter="api">API</button>
 <button class="option_item" data-filter="scss">SCSS</button>
</nav>

- js

const $filterBtn = document.querySelectorAll(".option_item");

$filterBtn.forEach((button) => {
  button.addEventListener("click", function () {
    const filter = button.getAttribute("data-filter");

    setTimeout(() => {
      filterProjects(filter);
    }, 250);
  });
});

function filterProjects(filter) {
  if (!projectList) {
    return;
  }

  // 모든 프로젝트 아이템
  const projects = projectList.projects;

  // 필터링된 프로젝트를 저장할 배열
  const filteredProjects = [];

  if (filter === "all") {
    filteredProjects.push(...projects);
  } else {
    filteredProjects.push(
      ...projects.filter((project) => {
        // project.type 배열에 filter가 포함되어 있는지 확인
        return project.type.includes(filter);
      })
    );
  }

  // 필터링된 프로젝트를 화면에 출력
  makeProjectList(filteredProjects);
}

// 초기 필터링
filterProjects("all");
🔍 code review
1. $filterBtn
    : html 문서에서 선택한 데이터 값 저장
2. filterBtn.forEach((button) => { ... });
    : forEach를 사용해 모든 필터 버튼에 대한 클릭 이벤트 등록
3.  button.getAttribute("data-filter");
    : getAttribute를 사용해 클릭된 버튼의 "data-filter"속성을 읽은 후, 해당 필터 값을 저장
4. setTimeout(() => { ... }, 250);
    : 250ms 후에 아래 필터 작업 수행
5. function filterProjects(filter) { ... };
    : 필터가 "all"이면 모든 프로젝트 표시, 그렇지 않으면 선택된 필터와 일치하는 프로젝트만 표시
6. makeProjectList(filteredProjects);
    : 필터링된 프로젝트 목록을 화면에 출력하는 함수 호출
6. filterProjects("all");
    : 스크립트가 실행될 때, 초기 필터로 "all" 설정

 

2-2. 필터 버튼 클릭 시, 투명도 조절

•  버튼 클릭 시, .project_area에 fade-out 클래스 추가
•  setTimeout을 이용해 250ms 뒤에 fade-out 클래스 삭제

- scss 

.project_area { &.fade-out { opacity: 0; } }

- js

$filterBtn.forEach((button) => {
  button.addEventListener("click", function () {
    const filter = button.getAttribute("data-filter");

    $filterBtn.forEach((item, idx) => {
      $filterBtn[idx].classList.remove("active");
    });

    button.classList.add("active");
    swProjectWrapper.classList.add("fade-out");

    setTimeout(() => {
      swProjectWrapper.classList.remove("fade-out");
      filterProjects(filter);
    }, 250);
  });
});

 

 

2-3. ( 앨범형 / 리스트형 ) 보기 방식 변경

목표
• "data-"를 이용해 데이터 속성값 지정해 두기
• 버튼 클릭 시, 해당 버튼이 list라면 .project_area에 "list" 클래스 추가

- html

<nav class="view">
  <button class="active album" data-view="album">
    <div class="circle_wrap">
      <div class="circle"></div>
      <div class="circle"></div>
      <div class="circle"></div>
      <div class="circle"></div>
    </div>
  </button>
  <button class="list" data-view="list">
    <div class="line_wrap">
      <div class="line"></div>
      <div class="line"></div>
      <div class="line"></div>
    </div>
  </button>
</nav>

- js

const $viewBtn = document.querySelectorAll(".view button");
$viewBtn.forEach((button) => {
  button.addEventListener("click", function () {
    const view = button.getAttribute("data-view");

    $viewBtn.forEach((item, idx) => {
      $viewBtn[idx].classList.remove("active");
    });
    button.classList.add("active");
    swProjectWrapper.classList.add("fade-out");

    setTimeout(() => {
      swProjectWrapper.classList.remove("fade-out");
      if (view == "list") {
        swProjectWrapper.classList.add("list");
      } else {
        swProjectWrapper.classList.remove("list");
      }
    }, 250);
  });
});
🔍 code review
1. $viewBtn
    : html 문서에서 선택한 데이터 값 저장
2. $viewBtn.forEach((button) => { ... });
    : forEach를 사용해 모든 필터 버튼에 대한 클릭 이벤트 등록
3. button.getAttribute("data-view");
    : getAttribute를 사용해 클릭된 버튼의 "data-view"속성을 읽은 후, 해당 필터 값을 저장
4. setTimeout(() => { ... }, 250);
    : 250ms 후에 아래 작업 수행
5. if (view == "list") { ... } else { ... };
    : view가 "list"면 클래스 list 추가, 그렇지 않으면 클래스 list 삭제

 

2-4. 모바일형 필터 버튼

목표
•  768px 이하일 경우, mb_mobile로 전환
•  라벨에 클릭한 옵션의 텍스트로 교체

- html

<div class="mb_filter">
  <button class="label">
    <h3>filter</h3>
    <div class="more_btn">
      <span></span>
      <span></span>
    </div>
  </button>
  <ul class="option_list filter">
    <li class="active option_item" data-filter="all">All</li>
    <li class="option_item" data-filter="responsive">Responsive</li>
    <li class="option_item" data-filter="team">Team</li>
    <li class="option_item" data-filter="single">Single</li>
    <li class="option_item" data-filter="api">API</li>
    <li class="option_item" data-filter="scss">SCSS</li>
  </ul>
</div>

- js

const $label = document.querySelector(".label");
const $labelTitle = $label.querySelector("h3");
const $moreBtn = document.querySelector(".more_btn span");
const $option = document.querySelector(".option_list");
const $options = document.querySelectorAll(".option_item");

// 클릭한 옵션의 텍스트를 .label 안에 넣음
const handleSelect = (item) => {
  $labelTitle.innerHTML = item.textContent;
};

// 옵션 몰록이 열림/닫힘
const toggleOptions = () => {
  if ($option.classList.contains("on")) {
    $option.classList.remove("on");
    $moreBtn.classList.remove("on");
  } else {
    $option.classList.add("on");
    $moreBtn.classList.add("on");
  }
};

// 옵션 클릭시, 클릭한 옵션을 넘기고 열림/닫힘
$options.forEach((option) => {
  option.addEventListener("click", () => {
    handleSelect(option);
    toggleOptions();
  });
});

// 라벨 클릭시, 옵션 몰록이 열림/닫힘
$label.addEventListener("click", toggleOptions);

 

3. 스와이퍼 적용

이미지는 모자이크 처리된 상태입니다.

- js

const mySwiper = new Swiper(".swiper", {
  slidesPerView: "auto",
  spaceBetween: 12,
  navigation: {
    nextEl: ".swiper-button-next",
    prevEl: ".swiper-button-prev",
    clickable: true,
  },
  slidesOffsetBefore: 30,
  slidesOffsetAfter: 30,
  breakpoints: {
    768: {
      spaceBetween: 16,
      slidesOffsetBefore: 50,
      slidesOffsetAfter: 50,
    },
    1024: {
      spaceBetween: 20,
      slidesOffsetBefore: 70,
      slidesOffsetAfter: 70,
    },
  },
});
💡 참고
• slidesPerView : 한 슬라이드에 보여줄 개수
• spaceBetween : 슬라이드 사이 여백
• navigation : 버튼
• slidesOffsetBefore & slidesOffsetAfter : 슬라이드 시작 & 끝 부분 여백
• breakpoints: 반응형 설정 (초기값 모바일)

 

4. 반응형 레이아웃

4-1. @mixin / @include

1. _variables.scss에 브레이크포인트 변수 저장

_variables.scss

$breakpoint-tablet: 1024px;
$breakpoint-mobile: 768px;

2. _mixins.scss에 미디어쿼리를 위한 @mixin 정의

_mixins.scss

// breakpoint
@mixin tablet {
  // min-width: 769px
  // max-width: 1024px
  @media (min-width: #{$breakpoint-mobile + 1px}) and (max-width: #{$breakpoint-tablet}) {
    @content;
  }
}

@mixin mobile {
  // max-width: 768px
  @media (max-width: #{$breakpoint-mobile}) {
    @content;
  }
}

3. @include를 사용하여 @mixin 호출

@include tablet {
    padding: 0.5rem 1.25rem;
    font-size: 0.875rem;
}

 

5. 페이지 색상 변경 이벤트

✅ 목표
• 버튼 클릭 시, 페이지의 전체 색상이 변경
• 컬러가 부드럽게 전환

- scss

  .text_accent {
    transition: color 0.3s ease-in-out;
    color: $secondary-color;
  }
• 변경되어야 할 클래스에 transition 주기

 

- js

const primaryColorArr = ["#5186ee", "#878231", "#FF4539", "#766AF0", "#FF734D"];
const secondColorArr = ["#e0f3cd", "#DAD8FD", "#CFECF4", "#F0E98E", "#FDE4E5"];
const subColorArr = ["#D0DCFF", "#CFE0D8", "#F6EEE3", "#CDE2E7", "#FFEBAE"];
let currentIndex = 1
  // 0은 기본색, 1~은 이후 변경되는 색;

function changePrimaryColor() {
  // currentIndex를 사용하여 새로운 색상을 가져오기
  const newPrimaryColor = primaryColorArr[currentIndex];
  const newSecondColor = secondColorArr[currentIndex];
  const newSubColor = subColorArr[currentIndex];

  // 모든 루트 요소의 "--primary-color" 값을 변경
  const rootElements = document.querySelectorAll(":root");
  rootElements.forEach((root) => {
    root.style.setProperty("--primary-color", newPrimaryColor);
    root.style.setProperty("--secondary-color", newSecondColor);
    root.style.setProperty("--sub-color", newSubColor);
  });

  // currentIndex를 업데이트
  if (currentIndex < primaryColorArr.length - 1) {
    currentIndex++;
  } else {
    currentIndex = 0; // 배열의 끝에 도달하면 처음으로 돌아감
  }
}

const $colorBtn = document.querySelector(".color_change");

$colorBtn.addEventListener("click", function () {
  $colorBtn.classList.add("click");

  setTimeout(() => {
    $colorBtn.classList.remove("click");
  }, 300);
  
  setTimeout(() => {
    changePrimaryColor();
  }, 1000);
});

 

🔍 code review
1. const ...Arr = [ ... ];
: 변경될 색상들 배열에 저장
2. let currentIndex = 1;
: 처음 버튼을 클릭했을 때, 출력될 인덱스 (0은 scss에서 지정한 초기값이기에 1번째부터 출력시켜 준다.) 
3. const new...Color = ...ColorArr[currentIndex];
: 페이지의 색상 변경 : currentIndex에 해당하는 배열에서 새로운 색상 가져오기
4. root.style.setProperty("--primary-color", newPrimaryColor);
: root 요소의 css 변수 ''--primary-color"을 newPrimaryColor로 설정
5. // currentIndex를 업데이트
: 배열의 길이만큼 currentIndex의 숫자가 1씩 증가하다가, 배열의 끝에 도달하면 처음(0)으로 돌아감

 


🤔 아쉬운 점

설계의 중요성

마무리 단계에서 색상 변경 이벤트를 작성했다. 초기에는 단순히 scss 변수를 자바스크립트에서 가져오면 될 것이라고 생각하여 가벼운 생각으로 마지막에 작업했다. 그러나 작업에 들어가기 전에 scss는 css의 전처리기라 바로 연결하는 것은 불가능하다는 것을 깨닫게 되었다. 자바스크립트에서 변수를 이용하려면 webpack을 설정해야했다. 그러면 scss에서 변수를 export해서 자바스크립트에 import하면 해결된다.

 

웹팩을 설치해서 여러 번 시도해봤지만 결국 실패했다. 그래서 css에서 변수를 다시 정의하고 활용하는 방법을 택했다. 이런 문제를 피하기 위해 코드 작업에 착수하기 전에 세심한 설계가 얼마나 중요한지 깨닫게 되었다.

 

현재 단계는 일단 완료했지만, 다음 코드 개선 단계에서는 웹팩을 설치하고 변수를 내보낼 수 있도록 1순위로 두어야겠다. 

Comments