개발/Etc

토이프로젝트 Plus Typo

달리초이 2023. 3. 20. 23:19

plus typo 디자인 템플릿으로 만든 대표이미지

 

 

토이프로젝트를 하나 완성했다. 앞으로 수정하고 업데이트 할 일이 많아보이는데, 조급해하지 않고 천천히 하려고 한다. 의식의 흐름대로 쓴 작업 수기는 이미 네이버 블로그에 올렸기 때문에 여기선 거기서 못 다 한 로고 애니메이션을 코드와 함께 풀어볼까 한다.

 

완성된 사이트 : http://typo.co.kr/

 

Plus Typo

Combine photos and text to create an image for Instagram.

typo.co.kr

 

 

이전에 리액트를 깨작거리면서 자바스크립트에 대한 갈증이 많이 생겼었다. 자바스크립트도 잘 모르는데 대뜸 리액트부터 들이박다보니 너무 많이 허둥댔던 기억이 난다. 그래서 이번에 html, css과 함께 바닐라 자바스크립트만 가지고 작업을 마무리 했다. 어차피 Node를 쓸 정도의 기술이 필요했던 것도 아니어서, '오히려 좋아'를 외치며 재밌게 작업했다. 이번엔 조금이라도 기초를 다지자 싶어 기본적인 내용부터 다시 학습하면서 병행했다. 그러다보니 중간에 코드를 리팩토링하는 과정도 제법 빈번했다. 리팩토링할 수 있다는 것 자체가 학습효과를 방증하는 게 아니겠나 싶어서 다행이랄까. 그래서 그런지 초반과 후반의 코드 스타일이 달라진다.😂 이번에 진행하면서 알게 된 기초적인 내용이라도 좀 정리해서 남겨놔야겠다. 뒤돌아서면 까먹는 수준이라 나를 믿을 수 없다.

 

 

타이핑 하듯 움직이는 로고 모션만들기

네이버 블로그에 언급했지만, 특정 로고 형태로 고정시키지 않고 가변형 로고를 만들면서 자바스크립트로 간단한 애니메이션을 곁들이게 됐다.

 

 

위 gif 이미지처럼 빠르진 않고 10초에 한 번씩 변경된다.

로직은 아래와 같다.

 

1. 여러가지 스타일의 폰트 랜덤 선택
2. 폰트마다 사이즈가 달라 로고가 바뀔때마다 가로 길이에 따라 파란 박스가 자동으로 늘어나야 됨
3. 파란박스 둘레의 6개의 작은 박스도 거기에 맞게 재정렬되어야 함
4. 타이핑 하듯 커서가 깜빡거리면서 쫓아다닐 것
5. 10초에 한 번씩 한글자씩 지워지고 다시 한글자씩 나타나야 됨
6. 사용된 폰트의 이름을 로고 아래 보여줄 것(페이드인/아웃)
7. 다른 브라우저 탭으로 옮겨가는 등 화면에서 벗어나면 브라우저 리소스를 위해 잠시 멈출 것
8. 화면 안에 다시 돌아오면 다시 재생할 것

 

제약사항을 대략적으로 정리하고 하나씩 처리했다. 폰트 이름을 제외하면 200줄이 안된다.

전부는 아니고 몇 가지 언급하자면,

 

const randomLetterStyle = () => {
  addLogoText();
  plus.style.fontFamily = randomFont();
  typo.style.fontFamily = randomFont();
  setTimeout(deleteLogoText, CHANGE_LOGO_TIME - 2000);
};

기본 뼈대는 로고를 한글자씩 쓰는 addLogoText 함수와 거기 랜덤폰트를 입히는 함수, 그리고 setTimeout을 이용해 한글자씩 지우는 함수로 이루어져 있다. 

 

커서의 깜빡임은 '|' 이 텍스트를 적당한 사이즈와 굵기 등 스타일을 넣고, opacity를 0과 1로 주기적으로 변형하도록 했다.

const cursorBlink = () => {
  fontCursor.style.opacity = fontCursor.style.opacity === "0" ? "1" : "0";
};
setInterval(cursorBlink, cursorBlinkTime);

 

로고의 상태를 다루는 건 클래스를 활용해 봤는데, 이게 맞나 싶은 느낌이라서 솔직히 자신은 없다. (하긴 자신있는 코드가 하나도 없는데 무슨 쓸데없는 고민을...😇)

class LogoState {
  isLogoVisible;
  visibility;

  constructor() {
    this.isLogoVisible = this.getLogoVisibility();
    this.visibility = setInterval(randomLetterStyle, CHANGE_LOGO_TIME);
  }

  getLogoVisibility(visible) {
    let boundary = logo.getBoundingClientRect().top > -100;
    if (visible === 'visible' && boundary === true) {  // visiblityState event
      return true;
    }
    if (this.visibility > 0 && boundary === true) { // 이미 진행중인 경우
      return null;
    } else if (boundary === true && (this.visibility === undefined || this.isLogoVisible === null)) {
      return boundary;
    } else if (this.visibility > 0 && boundary === false) { // 로고가 지정영역을 벗어난 경우
      return boundary;
    }
  }

  getLogoState() {
    return this.isLogoVisible;
  }

  setLogoState(visible) {
    this.isLogoVisible = this.getLogoVisibility(visible);
  }

  getVisibility() {
    return this.visibility;
  }

  handleLogoEvent() {
    if (this.isLogoVisible === null || this.isLogoVisible === undefined) {
      return;
    }
    if (this.isLogoVisible) {
      this.visibility = setInterval(randomLetterStyle, CHANGE_LOGO_TIME);
    } else if (!this.isLogoVisible) {
      clearInterval(this.visibility);
      this.visibility = undefined;
    }
  }

  handleLogoChange(visible) {
    this.setLogoState(visible);
    this.handleLogoEvent();
  }
}

 

이 클래스를 통해 스크롤 이벤트 시 화면에서 들락거리게 되는 것과 다른 브라우저 탭 등으로 이탈할 때 상태를 체크하여 멈추거나 다시 재생시키도록 처리했다. 이탈 체크는 visibilitychange라는 이벤트가 존재해서 처음 고민했던 것과 달리 생각보다 쉽게 해결됐다. MDN에 따르면 브라우저 탭의 컨텐츠가 visible 또는 hidden 상태로 변화할 때 발생된다고 한다. 여러가지 상황에서 이용해 볼 수 있겠단 생각이 든다.

 

document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "visible") {
    logoState.handleLogoChange('visible');
  } else {
    clearInterval(logoState.getVisibility());
  }
});

 

좀 많이 헤맸던 건 글자를 지우고 다시 쓰는 동작이었는데, setInerval을 처음 써보다보니 딱 원하는 순간에 멈추거나 다시 재생시키는 컨트롤이 어려웠다. 잘 학습해서 깔끔하게 한방에 끝냈다기 보다는 계속 테스트 해보면서 몸으로 떼운 느낌이다.(헷갈) 이번에도 역시 MDN setInterval문서를 참고했다. 내 작업의 포인트는 interval ID를 반환받아 clearInterval() 함수를 적절한 시점에 잘 써주는 것이었다.

 

const addLogoText = () => {
  const plusLoop = setInterval(() => {
    if (plus.innerText.length < 4) {
      plus.innerHTML += getLogoText(LOGO_TEXT[currentIndex]);
      currentIndex++;
    }
    if (currentIndex === 4) {
      clearInterval(plusLoop);

      const typoLoop = setInterval(() => {
        if (typo.innerText.length < 4) {
          typo.innerHTML += getLogoText(LOGO_TEXT[currentIndex]);
          currentIndex++;
        }
        if (currentIndex === 8) {
          currentIndex = 0;
          fadeInLogoFont();
          plusFont.innerHTML = plus.style.fontFamily.replace(/\"/gi, "");
          typoFont.innerHTML = typo.style.fontFamily.replace(/\"/gi, "");
          setTimeout(fadeOutLogoFont, CHANGE_LOGO_TIME - 2000);
          clearInterval(typoLoop);
        }
      }, 80);
    }
  }, 50);
};

const deleteLogoText = () => {
  const typoLoop = setInterval(() => {
    typo.innerHTML = typo.innerHTML.slice(0, -1);
    if (typo.innerHTML.length === 0) {
      clearInterval(typoLoop);

      const plusLoop = setInterval(() => {
        plus.innerHTML = plus.innerHTML.slice(0, -1);
        if (plus.innerHTML.length === 0) {
          clearInterval(plusLoop);
        }
      }, 80);
    }
  }, 50);
};

 

 

다음에는 가장 많이 사용한 canvas에 대해서 실제 사용한 코드와 함께 정리해 볼까 한다.

자바스크립트를 쓰면 쓸수록 더 잘 쓰고 싶은 욕구가 생긴다. 지금 당장 할 수 있는 게 훨씬 많아지는 기분이랄까. 두뇌야 힘내.

728x90
반응형