GSAP을 활용한 clip-path 애니메이션

keon lee·2025년 2월 3일

GSAP

목록 보기
2/2
post-thumbnail

오늘은 GSAP을 사용하여 clip-path 애니메이션을 만들어보았습니다.


우선 위에 영상과 같이 만들기 위해서는

HTML

HTML에서 배경 부모를 만들고 안에 배경을 몰아 넣어주었다.


CSS

.bg {
    position: absolute; // positon:absolute로 한 곳에 모아두기
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    clip-path: inset(0% 0 0% 0); // 초기 clip-path 설정

    @for $i from 1 through 14 {
        &:nth-child(#{$i}) {
            z-index: -#{$i}; // 초기 z-index 역순으로 하여 첫 이미지가 위로
            @include bg('파일명');
        }
    }
}
  • positon:absolute: 배경 한 곳에 모아두기
  • clip-path: inset(0): 첫 이미지 보이게 설정
  • z-index: - 숫자로 역순으로 배치하여 첫 이미지가 위로 올라오게 설정

JS

우선 전체적으로 mouseenter 이벤트가 필요했고, 각 item에 이벤트가 필요하여 반복문을 돌려주었다.

이벤트

$bgItem.each((idx, item) => {
    $(item).on('mouseenter', function() {
		``` 생략 ```
    });
});

Up & Down 감지

애니메이션을 낮은 순서에서 높은 순서의 아이템으로 이동 시, 높은 순서에서 낮은 순서로 이동 시를 다르게 표현하기 위해서 인덱스의 높낮이를 감지할 필요가 있었다.

그래서 변수를 설정하여 이전 인덱스와 현재 인덱스를 비교하고 저장하는 방법이 필요했다.

let activeIdx = 0;  // 현재 활성화된 index를 저장하는 변수

// 이벤트 콜백 함수 내
const dir = idx > activeIdx ? 'up' : 'down'; // 현재 idx가 기존 activeIdx보다 크면 'up', 아니면 'down'

activeIdx = idx; // 현재 활성화된 인덱스를 갱신

애니메이션

애니메이션은 clip-path로 이미지를 크롭시키는 방향으로 진행하였다.

const except = `.sc-work__bg-area .bg:not(:nth-child(${idx + 1}))`;
const target = `.sc-work__bg-area .bg:nth-child(${idx + 1})`;

현재 이벤트를 받는 대상과 아닌 대상을 구분할 필요가 있었고, GSAP은 CSS 선택자를 활용할 수 있기 때문에 :nth-child:not을 사용하여 구분지었다.

const bgAnimation = gsap.timeline({ overwrite: 'auto' })
.to(except, { 
    clipPath: () => dir === 'up' ? 'inset(100% 0 0% 0)' : 'inset(0% 0 100% 0)', 
    onComplete: () => {
        gsap.set(target, { zIndex: ++count })
    }
})
.fromTo(target, { 
    clipPath: () => dir === 'up' ? 'inset(0% 0 100% 0)' : 'inset(100% 0 0% 0)',  
}, {
    clipPath: 'inset(0% 0 0% 0)' 
}, '<');

clip-path(top right bottom left); 중
올라갈 때

  • except : top: 0 -> 100%
  • target : bottom: 100% -> 0% || top: 100% -> 0%

내려올 때는 반대로 설정하여 애니메이션을 완성했다.

그리고 이벤트가 자주 많이 일어날때 이전 애니메이션이 끝나기 전에 실행될 수 있으므로

overwrite: auto로 설정하여 이벤트 중복 방지 설정까지 해줬다.


최종 코드

$workItem.each((idx, item) => {
    let activeIdx = -1; // 처음에는 어떤 항목도 활성화되지 않음
    let isFirstHover = true; // 처음 hover인지 확인하는 변수

    $(item).on('mouseenter', function() {
        const dir = idx > activeIdx ? 'up' : 'down';
        const except = `.sc-work__bg-area .bg:not(:nth-child(${idx + 1}))`;
        const target = `.sc-work__bg-area .bg:nth-child(${idx + 1})`;

        if (isFirstHover) {
            // 처음 hover하는 경우 애니메이션 없이 바로 보이게 설정
            gsap.set(target, { clipPath: 'inset(0% 0 0% 0)', zIndex: ++count });
            isFirstHover = false; // 첫 hover 처리가 끝났음을 표시
        } else {
            // 기존 동작 (애니메이션 실행)
            gsap.timeline({ overwrite: 'auto' })
                .to(except, { 
                    clipPath: () => dir === 'up' ? 'inset(100% 0 0% 0)' : 'inset(0% 0 100% 0)', 
                    onComplete: () => {
                        gsap.set(target, { zIndex: ++count });
                    }
                })
                .fromTo(target, { 
                    clipPath: () => dir === 'up' ? 'inset(0% 0 100% 0)' : 'inset(100% 0 0% 0)',  
                }, {
                    clipPath: 'inset(0% 0 0% 0)' 
                }, '<');
        }

        activeIdx = idx;
    });
});

0개의 댓글