ScrollMagic
을 활용한 이미지 시퀀스 스크롤 애니메이션 구현하기 withGSAP
애플 제품 소개 페이지에서 자주 사용되는 사용자 경험 중 하나인 이미지 시퀀스 애니메이션은, 스크롤 이동 정도에 따라 이미지의 프레임이 움직이면서 마치 영상이 재생되는 듯한 효과를 보여줍니다.
이번 포스팅에서는 ScrollMagic
과 GSAP
라이브러리를 사용하여 스크롤 애니메이션을 구현해 보겠습니다 🤗
TweenMax
와 ScrollMagic
라이브러리를 CDN으로 설치합니다.
Tween을 ScrollMagic으로 제어하기 위해 animation.gsap 플러그인도 함께 설치할게요
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/ScrollMagic.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/plugins/animation.gsap.min.js"></script>
그리고 디버깅용 플러그인을 설치합니다. 애니메이션의 시작과 끝 지점을 화면에 표시해 주기 때문에 작업할 때 매우 편리합니다!
<script src="https://cdnjs.cloudflare.com/ajax/libs/ScrollMagic/2.0.7/plugins/debug.addIndicators.min.js"></script>
라이브러리 설치가 완료되었으면 우리에게 필요한 것은 컨트롤할 이미지입니다!
이미지 시퀀스 애니메이션은 영상이 재생되는 것처럼 보이는 효과라고 말씀드렸는데요, 그래서 우리는 영상의 한 프레임을 한 장의 이미지로 쪼개서 준비해야 합니다. 프레임 수가 높은 영상일수록 끊기지 않고 재생 (스크롤링) 되겠죠?
영상을 이미지로 변환하는 사이트에서 간단하게 이미지를 추출해서 준비해 봅시다.
파일명은 avata-x
로 지정할게요.
시작 이미지 avata-1
에서 스크롤을 내리면 avata-2
, avata-3
, avata-4
와 같이 avata- 뒤의 숫자가 커지고, avata-4
에서 스크롤을 올리면 avata-3
, avata-2
, avata-1
처럼 숫자가 작아질 거예요.
그렇기 때문에 모든 연속된 숫자로 지정해야 합니다.
<div class="wrap">
...
<div id="trigger" class="pinned">
<img class="avata" src="images/avata-1.png" alt="아바타 캐릭터 이미지 시퀀스">
<h2 class="text">Hello world!</h2>
</div>
</div>
스크롤을 움직일 때에도 아래 이미지의 초록색 부분은 고정되어 있습니다.
div.wrap
이 배경 영역이고 div.pinned
이 초록색 부분이 됩니다.
이미지의 파일명을 for 문으로 이미지 개수만큼 반복해서 배열을 생성하여 이미지를 정의합니다.
const $avataImgSqc = new Array();
for (let i = 1; i < 48; i++) {
$avataImgSqc.push(`images/avata-${i}.png`);
}
TweenMax.to( target:Object, duration:Number, vars:Object )
문법에 따라 애니메이션 오브젝트를 생성해 보겠습니다 😉
이번 포스팅에서는 .to()
메서드를 사용하겠습니다. 더 많은 메서드는 GreenSock Docs 를 참고하세요.
const $img = { crntImg: 0 },
$imgTag = document.querySelector('.avata');
let $tween_avata = TweenMax.to($img, 1, {
crntImg: $avataImgSqc.length - 1, // crntImg(0)부터 전체 이미지 개수 animate 속성
roundProps: 'crntImg', // 배열 인덱스로 사용되기 때문에 정수만 입력
immediateRender: true, // 첫번째 이미지 자동로딩 여부
onUpdate: function () {
$imgTag.setAttribute('src', $avataImgSqc[$img.crntImg]) // 이미지 소스 세팅
}
});
이제 ScrollMagic에 대입해 봅시다. 먼저 new
키워드로 ScrollMagic Controller 인스턴스를 생성합니다.
const controller = new ScrollMagic.Controller();
Scene 오브젝트를 생성합니다. Scrollmagic Docs 참고하시면 다양한 메서드와 옵션을 확인하실 수 있습니다!
new ScrollMagic.Scene();
이제 만들어 둔 애니메이션 오브젝트를 Scene에 추가 후 마지막으로 Controller에 추가합니다.
new ScrollMagic.Scene()
.setTween($tween_avata)
.addTo($controller);
스크롤 이동시 고정될 영역과 Scene이 시작될 트리거를 삽입해 봅시다! addIndicators()
를 추가해서 어떤 지점에서 애니메이션이 시작되는지 확인해 볼게요
new ScrollMagic.Scene({
triggerElement: '#trigger'
})
.setTween($tween_avata)
.setPin('.pinned')
.addTo($controller)
.addIndicators();
그러나 지금은 start 지점 = end 지점이기 때문에, start 지점을 지나는 순간 앞서 배열로 만들어뒀던 for 문이 돌면서 이미지 시퀀싱이 한 번에 실행되어버립니다 🤯
end 지점을 정하기 위해 Scene의 옵션 중 duration
을 사용해서 .pinned
의 길이를 지정하겠습니다. 임의로 5000으로 지정해 볼게요.
triggerHook
으로 trigger가 일어날 지점의 위치를 지정할 수 있는데요. 0 = viewport 기준 가장 상단, 1= 가장 하단입니다.
new ScrollMagic.Scene({
duration: 5000,
triggerElement: '#trigger',
triggerHook: 0,
})
.setTween($tween_avata)
.setPin('.pinned')
.addTo($controller);
자 이제 #trigger
를 지나 5000만큼 스크롤 하는 동안 .pinned
영역이 고정되면서 img 태그의 src 속성값이 바뀌게 됩니다.
<img class="avata" src="images/avata-1.png">
<img class="avata" src="images/avata-2.png">
<img class="avata" src="images/avata-3.png">
...
이미지의 로딩이 느리다면 프레임이 뚝뚝 끊기는 것처럼 보일 수 있겠죠?
이미지를 미리 불러오는 방법으로 해결할 수 있습니다 🤟
function preloading(preImgs) {
let imgTotal = preImgs.length;
for (let i = 0; i < imgTotal; i++) {
let img = new Image();
img.src = preImgs[i];
}
}
const $avataImgSqc = new Array();
for (let i = 1; i < 48; i++) {
$avataImgSqc.push(`images/avata-${i}.png`);
}
function preloading(preImgs) {
let imgTotal = preImgs.length;
for (let i = 0; i < imgTotal; i++) {
let img = new Image();
img.src = preImgs[i];
}
}
preloading($avataImgSqc)
const $img = { crntImg: 0 },
$imgTag = document.querySelector('.avata');
let $tween_avata = TweenMax.to($img, 1, {
crntImg: $avataImgSqc.length - 1,
roundProps: 'crntImg',
immediateRender: true,
onUpdate: function () {
$imgTag.setAttribute('src', $avataImgSqc[$img.crntImg])
}
});
new ScrollMagic.Scene({
duration: 5000,
triggerElement: '#trigger',
triggerHook: 0
})
.setTween($tween_avata)
.setPin('.pinned')
.addTo($controller);
참고 : https://scrollmagic.io/examples/expert/image_sequence.html