127.0.0.1_5500_.png

제작 기간: 2024.06 ~ 2024.06 기여도: 퍼블리싱 100% 사용 기술: HTML, SCSS, JavasScript(ES6+), GSAP URL: https://kimbangul.github.io/pp-fragment/

GSAP 라이브러리를 활용한 인터랙션 구현에 중점을 두어 클론 코딩한 사이트입니다.

🔍Overview


반복되는 DOM 삽입하기

인트로 페이지의 유리조각 이미지(약 25개), Glyph Set 부분의 문자들(약 4nn개), select 태그의 option 등 반복되는 DOM을 일일이 마크업하는 것은 비효율적으로 생각되어 javascript를 통해서 DOM을 추가하였습니다.

Untitled

Untitled

// FUNCTION 특정 요소를 선택 후, 해당 요소 하위에 dom을 추가한다.
const insertDom = (selector, domArray) => {
  /** selector: 하위 요소를 추가할 dom, domArray: 추가할 하위 요소로 구성된 array  */
  const tag = document.querySelector(selector);
  for (let i = 0; i < domArray.length; i++) {
    tag.insertAdjacentHTML('beforeend', domArray[i]);
  }
};

// FUNCTION 메인 이미지 넣는 함수
const setMainBg = () => {
  const node = Array(25)
    .fill('')
    .map((el, idx) => {
      return `<div class='main-bg-item'><img src="assets/img/main/fragment-${
        idx + 1
      }.png" alt=""></div>`;
    });

  insertDom('.main-bg', node);
};

FUNCTION language 추가하는 함수
const setLanguageList = () => {
  const languages = [
    [
      'Afrikaans',
      'Basque',
      'Breton',
      'Catalan',
      'Croatian',
      'Czech',
      'Danish',
      'Dutch',
      'English',
    ],
 ...
    [
      'Slovak',
      'Slovenian',
      'Spanish',
      'Swahili',
      'Swedish',
      'Turkish',
      '(And more)',
    ],
  ];
  const languageNode = languages.map((el, idx) => {
    return `<ul class="language-list">${el
      .map((lang) => `<li class="language-item">${lang}</li>`)
      .join('')}</ul>`; // .join()을 사용하지 않으면 요소들이 ','로 연결됨
  });

  insertDom('.language', languageNode);
};

insertAdjacentHTML API와 템플릿 문자열을 활용하면 React와 같이 반복되는 DOM을 쉽게 추가할 수 있습니다.

랜덤으로 텍스트 포지션과 위치 변경

Untitled

// FUNCTION 랜덤 수 구하는 함수
const getRandomInt = (max) => {
  return Math.floor(Math.random() * max);
};

// FUNCTION 랜덤 텍스트 애니메이션
const onRandomizeText = () => {
  const textArr = document.querySelectorAll('.random-text');

  const weight = [100, 200, 400, 500, 600, 700, 800, 900];

  textArr.forEach((el) => {
    const filteredWeight = weight.filter(
      (wght) => wght !== el.style.fontVariationSettings // 현재 weight를 제외한 weight 리스트 만들기
    );

    const newWeight = getRandomInt(filteredWeight.length);

    el.style.fontVariationSettings = `"wght" ${filteredWeight[newWeight]}`;
    ...

max까지의(max 제외) 랜덤한 수를 구하는 함수를 만든 뒤, 이를 이용하여 폰트 weight 리스트가 나열된 weight 배열에서 현재 적용되어 있는 weight를 제외하고 랜덤으로 weight를 적용할 수 있도록 구현하였습니다.

const onRandomizeText = () => {
...
const windowWidth = window.innerWidth;
  if (windowWidth < 700) return; // 모바일 화면 사이즈에서는 포지션을 이동하지 않습니다.
  
  // 텍스트 포지션 변경
  textArr.forEach((el, idx) => {
    const center = el.offsetParent.offsetWidth / 2 - el.offsetWidth / 2; // 이동시켜야하는 텍스트 요소의 중간 width값
    const parentVw = (el.offsetParent.offsetWidth / windowWidth) * 100;
    const centerVw = (center / windowWidth) * 100;

    const pos = [0, `${centerVw}vw`, `calc(${parentVw}vw - 100%)`];
    // 컨테이너의 width만큼 이동한 뒤, 자기 자신의 width만큼(-100%) 빼면 끝 점이 컨테이너 안으로 들어옴
    const randomPos = pos[getRandomInt(pos.length)];

    if (![3, 4].includes(idx)) {
      // 인덱스가 3,4가 아닐 때
      el.style.transform = `translateX(${randomPos})`;
    } else if (idx === 3) { // idx가 3번, 4번인 요소는 한 줄에 있으며, 양 끝단으로만 움직임.
      const inner = document.querySelector('.random-text-inner');
      const parentVw = (inner.offsetParent.offsetWidth / windowWidth) * 100;
      const pos = [0, `calc(${parentVw}vw - 100%)`];
      const randomPos = pos[getRandomInt(pos.length)];

      inner.style.transform = `translateX(${randomPos})`;
    }
  }

weight를 먼저 변경한 후 position을 이동해 주었는데, 반응형 대응을 위해 이동해야하는 거리를 뷰포트 단위로 변환하는 작업을 하였습니다. weight를 변경할 때와 동일한 방식으로 배열에서 랜덤한 포지션을 선택하였으며, 인덱스가 3,4번인 요소(AS LONG AS, YOU)만 포지션이 다르기 때문에 예외 처리를 해 주었습니다.