가장 먼저 CSS transition 속성을 이용해서 아코디언 애니메이션을 구현했습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
.box {
height: 0;
overflow: hidden;
transition: all 0.3s ease-out;
}
</style>
</head>
<body>
<button class="button" type="button">클릭</button>
<div class="box">
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
</div>
<script>
const box = document.querySelector('.box');
const button = document.querySelector('.button');
let visible = false;
button.addEventListener('click', () => {
!!visible ? expandSection(box) : collapseSection(box);
visible = !visible;
});
const collapseSection = element => {
element.style.height = '';
};
const expandSection = element => {
const sectionHeight = element.scrollHeight;
element.style.height = `${sectionHeight}px`;
};
</script>
</body>
</html>
CSS를 수정하지 않고 애니메이션 구현하기
- window.requestAnimationFrame 함수를 사용해서 구현했습니다.
브라우저가 화면에 무언가를 그리기까지는 여러 단계가 존재합니다.
애니메이션 및 기타 작업들을 수행하는 Javascript
CSS 규칙을 어떤 요소에 적용할지 계산하는 프로세스인 Style
브라우저가 요소에 어떤 규칙을 적용할지 알게 되면 화면에서 얼마의 공간을 차지하고 어디에 배치되는지 계산하는 프로세스인 Layout(reflow)
픽셀을 채우는 프로세스인 Paint(redraw)
이전의 작업들이 개별적인 레이어에서 진행되고 이를 합치는 프로세스인 Composite 로 진행됩니다.
requestAnimationFrame()은 화면에서 변화가 발생할 때 개발자는 브라우저에서 정확한 시간(프레임 시작 시)에 작업을 수행해야 매끄러운 움직임을 수행할 수 있습니다.
이 메소드는 실제 화면이 갱신되어 표시되는 주기에 따라 함수를 호출해주기 때문에 자바스크립트가 프레임 시작 시 실행되도록 보장합니다.
보통 1초에 60회 정도 실행이 되지만 대부분의 브라우저는 W3C 권장사항에 따라 디스플레이 주사율과 일치하도록 실행됩니다.
setTimeout(), setInterval()은 보이지 않은 곳에서도 수행되지만, requestAnimationFrame()는 현재 창에 표시 되지 않으면 애니메이션을 중지하여 배터리 수명과 성능향상에 도움이 됩니다
즉, requestAnimationFrame()을 사용하면 브라우저가 리소스 사용을 더욱 최적화하고 애니메이션을 더욱 부드럽게 만들 수 있습니다.
출처 : https://fullest-sway.me/blog/2019/01/28/requestAnimationFrame/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<style>
.box {
height: 0;
overflow: hidden;
}
</style>
<body>
<button class="button" type="button">클릭</button>
<div class="box">
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
</div>
<script>
const element = document.querySelector('.box');
const button = document.querySelector('.button');
const scrollHeight = element.scrollHeight;
let progress = scrollHeight * 10;
let visible = false;
button.addEventListener('click', () => {
!!visible
? window.requestAnimationFrame(unStep)
: window.requestAnimationFrame(step);
visible = !visible;
});
const step = () => {
if (progress >= scrollHeight * 10) progress = 0;
if (progress <= scrollHeight * 10) progress += 16;
element.style.height = Math.min(progress / 10, scrollHeight) + 'px';
if (progress < scrollHeight * 10) {
window.requestAnimationFrame(step);
}
};
const unStep = () => {
if (progress <= 0) progress = scrollHeight * 10;
if (progress > 0) progress -= 16;
element.style.height = Math.max(progress / 10, 0) + 'px';
if (progress > 0) {
window.requestAnimationFrame(unStep);
}
};
</script>
</body>
</html>
- 애니메이션을 모두 기다린 후에 클릭을 하면 잘 작동하지만 애니메이션 도중 클릭을 할 경우 제대로 작동하지 않고 다시 클릭 했을 때 2번 작동 되는 경우가 발생했습니다.
- 펴지는 동작과 접히는 동작이 같이 일어나서 움직이지 않는거처럼 보인 이후 한쪽 함수가 많아지면 한쪽으로 수행 된 후 나머지 함수가 실행 되었습니다.
- ex)
- 클릭
- (+ 16) 플러스 애니메이션 시작
- 클릭
- (+16) + (-16) 애니메이션 멈춤
- 클릭
- (+16) + (-16) + (+16) 플러스 애니메이션 시작
- 플러스 애니메이션 종료
- (-16) 마이너스 애니메이션 실행
- 마이너스 애니메이션 종료
애니메이션 중 추가 애니메이션 생성 방지
- 애니메이션을 하는 함수를 취소하려고 했지만 Javascript가 싱글스레드이기 때문에 함수 취소가 어렵다는 결론만 얻고 우선적으로 애니메이션 동작되는 동안 이상 동작을 막기 위해 애니메이션 중에는 click에 따른 이벤를 실행하지 않게 하였습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<style>
.box {
height: 0;
overflow: hidden;
}
</style>
<body>
<button class="button" type="button">클릭</button>
<div class="box">
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
</div>
<script>
let start = false;
const element = document.querySelector('.box');
const button = document.querySelector('.button');
const scrollHeight = element.scrollHeight;
let progress = scrollHeight * 10;
let visible = false;
button.addEventListener('click', event => {
if (!start) {
!!visible
? window.requestAnimationFrame(unStep)
: window.requestAnimationFrame(step);
}
});
const step = () => {
start = true;
if (progress >= scrollHeight * 10) progress = 0;
if (progress <= scrollHeight * 10) progress += 16;
element.style.height = Math.min(progress / 10, scrollHeight) + 'px';
if (progress < scrollHeight * 10) {
window.requestAnimationFrame(step);
} else {
start = false;
visible = !visible;
}
};
const unStep = () => {
start = true;
if (progress <= 0) progress = scrollHeight * 10;
if (progress > 0) progress -= 16;
element.style.height = Math.max(progress / 10, 0) + 'px';
if (progress > 0) {
window.requestAnimationFrame(unStep);
} else {
start = false;
visible = !visible;
}
};
</script>
</body>
</html>
애니메이션 도중에 다른 애니메이션 실행
- 애니메이션 함수가 연속적으로 발생하는 것이었기 때문에 취소하는 것이 아니라 실행하는 함수를 막는 것이 목적이고 애니메이션 함수가 click callback이 끝나고 실행 된다는 점을 고려하여 조건을 추가하여 문제를 해결했습니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<style>
.box {
height: 0;
overflow: hidden;
}
</style>
<body>
<button class="button" type="button">클릭</button>
<div class="box">
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
내용을 만들어 봅시다.<br />
</div>
<script>
let start = false;
const element = document.querySelector('.box');
const button = document.querySelector('.button');
const scrollHeight = element.scrollHeight;
let progress = scrollHeight * 10;
let visible = false;
button.addEventListener('click', event => {
!!visible
? window.requestAnimationFrame(unStep)
: window.requestAnimationFrame(step);
visible = !visible;
});
const step = () => {
if (progress >= scrollHeight * 10) progress = 0;
if (progress <= scrollHeight * 10) progress += 16;
element.style.height = Math.min(progress / 10, scrollHeight) + 'px';
if (progress < scrollHeight * 10 && !!visible) {
window.requestAnimationFrame(step);
}
};
const unStep = () => {
if (progress <= 0) progress = scrollHeight * 10;
if (progress > 0) progress -= 16;
element.style.height = Math.max(progress / 10, 0) + 'px';
if (progress > 0 && !visible) {
window.requestAnimationFrame(unStep);
}
};
</script>
</body>
</html>