제작 기간: 2024.06 ~ 2024.06 기여도: 퍼블리싱 100% 사용 기술: HTML, SCSS, JavasScript(ES6+), GSAP URL: https://kimbangul.github.io/pp-fragment/
GSAP 라이브러리를 활용한 인터랙션 구현에 중점을 두어 클론 코딩한 사이트입니다.
인트로 페이지의 유리조각 이미지(약 25개), Glyph Set 부분의 문자들(약 4nn개), select 태그의 option 등 반복되는 DOM을 일일이 마크업하는 것은 비효율적으로 생각되어 javascript를 통해서 DOM을 추가하였습니다.
// 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을 쉽게 추가할 수 있습니다.
// 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
)만 포지션이 다르기 때문에 예외 처리를 해 주었습니다.