HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🕵
Web Animation 1부 css Animation
/
⛳
ch3 - 3. Keyframe
⛳

ch3 - 3. Keyframe

Index

Index0. File download1. Keyframe1.1 Keyframe에 대하여1.2. Keyframe 규칙1.3 Animation 적용하기1.3.1 animation의 단일 속성들2. 실행해 보며 배우는 Keyframe 예제2.1 빌딩 올리기2.2 빌딩 2개 올리기2.3 빌딩 3개와 냥이집사님 배치2.4 빌딩 3개와 냥이집사님 걷게하기2.5 여러분을 위한 과제

0. File download

www.paullab.co.kr
www.paullab.co.kr
http://www.paullab.co.kr/building.zip

1. Keyframe

1.1 Keyframe에 대하여

@keyframes에 대하여 가벼운 예제를 보고 가도록 하겠습니다.
해당 @keyframes hojun을 설정하여 div의 animation으로 1초간격으로 주면 아래와 같이 실행되게 됩니다.
notion image
이처럼 Keyframe은 어떤 변화가 일어나는 지점을 설정하여 특정 시간 동안 해당 Property와 Value를 변화시킵니다.
+ transition과 animation의 차이점 transition과 animation 속성은 Javascript의 도움 없이 오브젝트에 직접 애니메이션 효과를 적용할 때 사용합니다. transition과 animation은 요소 상태에 대한 의존 여부에 대해 차이를 가집니다. transition은 요소의 상태가 변경되어야 애니메이션을 실행할 수 있지만, animation 속성은 요소의 상태 변화와 관계없이 애니메이션을 실행할 수 있습니다. 또한 animation 속성은 @keyframes 속성을 이용해 프레임을 추가할 수 있습니다.

1.2. Keyframe 규칙

@keyframes은 애니메이션이 만들어지는 부분입니다. from 속성이나 0% 속성에 설정한 스타일부터 to 속성이나 100% 속성에 설정한 스타일로 점차 변경되면서 애니메이션이 재생됩니다. 0%과 100% 사이에는 여러 개의 중간 속성을 설정할 수 있습니다.

1.3 Animation 적용하기

CSS3 애니메이션을 이용하면 여러 개의 CSS 스타일을 부드럽게 전환시킬 수 있습니다. @keyframes은 CSS 스타일의 변화 과정을 나타낼 때 사용됩니다. @keyframes을 이용한 애니메이션은 @transition보다 정밀한 효과를 구현할 수 있습니다.

1.3.1 animation의 단일 속성들

  • animation-name 애니메이션을 재생(호출) 하기 위해서는 반드시 이름을 정의해야 합니다. name은 @keyframes 속성에서 설정한 이름을 동일하게 사용합니다.
+ animation-name 이름 규칙 animation-name의 시작에는 영문 소문자, 숫자, 문자열, 언더바(_), 하이픈(-)을 사용합니다. 영문 대문자, 숫자, 특수문자는 사용할 수 없습니다(파일 및 폴더명에는 허용). 여러 개의 animation-name을 동시에 나열할 경우 ,를 사용합니다.
  • animation-duration duration 속성은 애니메이션의 시작부터 종료까지의 총 지속 시간을 설정할 때 사용하며, transition-duration과 유사한 기능을 제공합니다. duration 값은 양수로 지정해야 그 결과를 확인할 수 있습니다. 속성 값을 0 혹은 음수로 설정했을 경우 애니메이션이 실행되지 않습니다.
duration 속성의 결과는 아래의 전체 코드를 통해 확인할 수 있습니다.
  • animation-iteration-count 위의 duration 코드 예제를 실행시키면 애니메이션이 1회 재생 후 종료되는 것을 알 수 있습니다. iteration-count 속성은 애니메이션을 재생하는 횟수를 지정할 때 사용합니다. iteration-count 속성의 기본 값은 1이며, 0으로 값을 지정할 경우 애니메이션이 재생되지 않습니다. 음수로 속성 값을 지정할 경우 기본 값인 1과 같은 결과를 출력하고, 자연수가 아닌 양의 유리수(ex. 1.5)로 속성 값을 지정할 경우 애니메이션 재생 도중 첫 번째 프레임으로 돌아가 종료됩니다. infinite로 값을 설정할 경우 애니메이션을 무한 반복할 수 있습니다.
iteration-count 속성의 결과는 아래의 전체 코드를 통해 확인할 수 있습니다.
  • animation-direction 애니메이션의 재생 방향을 설정할 때 direction 속성을 사용합니다. direction 속성의 기본 값은 from 또는 0%에 설정된 스타일에서 to또는100%에 설정된 스타일대로 재생하는 normal 입니다. direction 속성의 종류는 다음과 같습니다.
alternate와 alternate-reverse는 서로 반대의 결과를 보여줍니다. alternate은 순방향으로 애니메이션을 시작해 실행 횟수가 홀수일 때에는 순방향으로, 짝수일 때에는 역방향으로 재생합니다. 반면, alternate-reverse은 역방향으로 애니메이션을 시작해 실행 횟수가 홀수일 때에는 역방향으로, 짝수 일 때에는 순방향으로 재생합니다. direction 속성의 결과는 아래의 전체 코드에서 확인할 수 있습니다.
  • animation-timing-function timing-function은 애니메이션 @keyframes 사이의 재생 속도를 조절하는 속성으로, transion-timing-function 속성과 유사한 결과를 제공합니다. timing-function 속성의 종류로는 ease, linear, ease-in, ease-out, ease-in-out, cubic-bezier(n, n, n, n) 등이 있습니다.
각 속성값에 대한 속도 그래프는 다음과 같으며, cubic-bezier의 n 값에 따른 변화는 https://cubic-bezier.com/ 에서 확인할 수 있습니다.
notion image
  • animation-delay 애니메이션 시작을 지연시키고 싶을 경우, delay 속성을 사용합니다. delay 속성의 기본 값은 애니메이션이 지연 없이 시작되는 0 또는 now입니다. 값이 음수일 경우 지정된 시간이 지난 뒤의 장면부터 지연 없이 애니메이션이 시작됩니다.
delay 속성의 결과는 아래의 전체 코드에서 확인할 수 있습니다.
  • animation-play-state 재생여부를 설정할 경우 play-state 속성을 사용합니다. 속성값이 running일 경우 애니메이션을 재생하고, paused일 경우 애니메이션을 정지합니다.

2. 실행해 보며 배우는 Keyframe 예제

2.1 빌딩 올리기

아래 예제(002.html)는 keyframes를 이용하여 background 이미지를 animation steps를 이용하여 순차적으로 옆으로 넘기며 마치 빌딩이 지어지는 것처럼 보이게 하는 예제입니다.
이 예제를 실행시키면 아래와 같은 이미지가 순차적으로 5초의 시간 간격을 가지고 세워지는 것처럼 보이는 것을 알 수 있습니다.
notion image
이 코드에서 from과 to는 0%와 100%입니다. 여기서 background-position: -3730px, 0 값이 to에 있는 이유는 이미지가 왼쪽 상단에 0, 0에서 옆으로 하나의 장면이 넘어가며 이미지의 Position은 결국 마이너스의 값을 가지게 되기 때문입니다.
또, animation: hojun 5s steps(33);에 스탭이 있는 이유는 이미지가 부드럽게 옆으로 넘어가는 것이 아니라 각각의 장면이 끊어지면서 건물을 보여줘야 하기 때문에 그렇습니다.

2.2 빌딩 2개 올리기

아래 예제(003.html)는 위와 같은 방식으로 2개의 건물을 올리는 코드입니다. 여기서 background-position에 마이너스 값은 이미지의 길이입니다. 스탭은 총 넓이를 각각의 이미지에 건물 이미지 넓이를 나눈 값입니다.

2.3 빌딩 3개와 냥이집사님 배치

이번에는 주인공인 냥이 집사님을 배치해보도록 하겠습니다. 좀 더 자유로운 포지셔닝을 위해 html코드는 아래와 같이 배치하였습니다.
여기서 back class에는 relative 속성을 주고 그 아래 있는 div는 absolute 속성을 부여하여 자유로운 배치가 가능하도록 하였습니다.
animation 부분만 한 번 보도록 하겠습니다. background에서 이미지 3개를 교체함으로 마치 냥이 집사님이 걷는 것처럼 구현하려 하였습니다. 여기에 냥이 집사님이 걸어야 하므로 Y축으로 이동을 하게 하였고, 냥이 집사님의 몸집이 너무 커서 scale(0.3) 의 속성을 부여하였습니다.
아래 코드는 모든 코드입니다.

2.4 빌딩 3개와 냥이집사님 걷게하기

다음 파일은(006.html) 위 파일에서 냥이 집사님이 좀 더 현실적으로 걷게 하는 코드입니다. %를 10%로 나누고 마지막에 냥이 집사님이 자연스럽게 걷다가 멈추게 하였습니다.
좀 더 세부적으로 말씀을 드리자면 가속도가 붙지 않게 하기 위해 animation: cat 3s linear;를 주었습니다. 또한 마지막에 냥이 집사님을 멈추게 하기 위해 animation-fill-mode: forwards;속성을 부여하였습니다.
%는 10%로 나눈 것에서 Y축 값을 -20px로 주었습니다.

2.5 여러분을 위한 과제

💡
이미지를 활용하여 좀 더 다양한 스토리 애니메이션을 만들어보세요. 친구들도(양, 여우, 토끼) 만나게 하시고, 나무도 배치하며, 보다 많은 건물들도 배치해보십시오.
뒤편에 JS까지 공부하시고 다시 이 챕터로 돌아와 공부해보시면 좀 더 많은 도움이 되실 것입니다.
 
<!DOCTYPE html> <html> <head> <style> /* 키프레임 이름 == 애니메이션 이름 */ @keyframes hojun { 0% { transform: translate(0, 0); } 100% { transform: translate(300px, 0) rotate(360deg) scale(2); } } div { margin: 200px; width: 100px; height: 100px; background-color: red; animation: hojun 1s; } </style> </head> <body> <div>hello world</div> </body> </html>
/* [ from ~ to 속성 ] */ @keyframes animation-name { /* 애니메이션의 시작 프레임 */ from { styles; } /* 애니메이션의 종료 프레임 */ to { styles; } /* [ 0% ~ 100% 속성 ] */ @keyframes animation-name { /* 애니메이션의 시작 프레임 */ 0% { styles; } 50% { styles; } /* 애니메이션의 종료 프레임 */ 100% { styles; }
/* 키프레임 이름 == 애니메이션 이름 */ @keyframes yoon { 0% { styles; } 100% { styles; } } div{ /* 애니메이션 이름 */ animation-name: yoon; }
/* [ 올바른 이름 예시 ] */ animation-name: yoon; /* 영문 소문자로 시작하는 이름 */ animtaion-name: _yoon; /* 언더바(_)로 시작하는 이름 */ animation-name: -yoon; /* 하이픈(-)으로 시작하는 이름 */ animation-name: yoon1, yoon2; /* 여러 개의 animation-name 나열 */ /* [ 잘못된 이름 예시 ] */ animation-name: Name-yoon; /* 영문 대문자로 시작하는 이름 */ animation-name: *-name-yoon; /* 특수문자로 시작하는 이름 */ animation-name: 1yoon; /* 숫자로 시작하는 이름 */
/* [ 애니메이션이 재생되지 않는 경우 ] */ animation-duration: 0; /* 재생시간이 0인 경우 */ animation-duration: -3s; /* 재생시간이 음수인 경우 */ /* [ 애니메이션이 재생되는 경우 ] */ animation-duration: 3s; /* 재생시간이 양수인 경우 */ animation-duration: 500ms; /* 1초 이하의 재생시간을 입력할 경우 */
<!DOCTYPE html> <html> <head> <style> @keyframes yoon { from {transform: translate(100px, 0);} to {transform: translate(300px, 0);} } div { position: absolute; margin: 50px; width: 100px; height: 100px; background-color: #ff0000; /* 애니메이션 이름 */ animation-name: yoon; } /* [ 재생시간이 0인 경우 ] */ .duration_0s { top: 20px; animation-duration: 0; } /* [ 재생시간이 음수인 경우 ] */ .duration_minus { top: 130px; animation-duration: -3s; } /* [ 재생시간이 양수인 경우 ] */ .duration_plus { top: 240px; animation-duration: 3s; } /* [ 1초 이하의 재생시간을 입력할 경우 ] */ .duration_ms { top: 350px; animation-duration: 500ms; } </style> </head> <body> <div class="duration_0s">0s</div> <div class="duration_minus">minus</div> <div class="duration_plus">plus</div> <div class="duration_ms">ms</div> </body> </html>
/* [ 애니메이션이 재생되지 않는 경우 ] */ animation-iteration-count: 0; /* 재생횟수가 0인 경우 */ animation-iteration-count: -3; /* 재생횟수가 음수인 경우 */ /* [ 애니메이션이 재생되는 경우 ] */ animation-iteration-count: 3; /* 재생횟수가 양수인 경우 */ animation-iteration-count: 1.5; /* 재생횟수가 실수인 경우 */ animation-iteration-count: infinite; /* 애니메이션을 무한 반복할 경우 */
<!DOCTYPE html> <html> <head> <style> @keyframes yoon { from {transform: translate(100px, 0);} to {transform: translate(300px, 0);}} div { position: absolute; margin: 50px; width: 100px; height: 100px; background-color: #ff0000; /* 애니메이션 속성 */ animation-name: yoon; animation-duration: 1s; } /* [ 재생횟수가 0인 경우 ] */ .iteration-count_0 { top: 20px; animation-iteration-count: 0; } /* [ 재생횟수가 음수인 경우 ] */ .iteration-count_minus { top: 130px; animation-iteration-count: -3; } /* [ 재생횟수가 양수인 경우 ] */ .iteration-count_plus { top: 240px; animation-iteration-count: 3; } /* [ 재생횟수가 유리수인 경우 ] */ .iteration-count_rational { top: 350px; animation-iteration-count: 1.5; } /* [ 애니메이션을 무한 반복할 경우 ] */ .iteration-count_infinite { top: 460px; animation-iteration-count: infinite; } </style> </head> <body> <div class="iteration-count_0">0</div> <div class="iteration-count_minus">minus</div> <div class="iteration-count_plus">plus</div> <div class="iteration-count_rational">rational</div> <div class="iteration-count_infinite">infinite</div> </body> </html>
animation-direction: normal; /* 순방향 재생 */ animation-direction: reverse; /* 역방향 재생 */ animation-direction: alternate; /* 순방향 시작, 순방향-역방향 번갈아 재생 */ animation-direction: alternate-reverse; /* 역방향 시작, 역방향-순방향 번갈아 재생 */
<!DOCTYPE html> <html> <head> <style> @keyframes yoon { from {transform: translate(100px, 0);} to {transform: translate(300px, 0);}} div { position: absolute; margin: 50px; width: 100px; height: 100px; background-color: #ff0000; /* 애니메이션 속성 */ animation-name: yoon; animation-duration: 1s; animation-iteration-count: infinite;} /* [ 순방향 재생 ] */ .direction_normal { top: 20px; animation-direction: normal;} /* [ 역방향 재생 ] */ .direction_reverse { top: 130px; animation-direction: reverse} /* [ 순방향 시작, 순방향-역방향 번갈아 재생 ] */ .direction_alternate { top: 240px; animation-direction: alternate;} /* [ 역방향 시작, 역방향-순방향 번갈아 재생 ] */ .direction_alternate-reverse { top: 350px; animation-direction: alternate-reverse;} </style> </head> <body> <div class="direction_normal">normal</div> <div class="direction_reverse">reverse</div> <div class="direction_alternate">alternate</div> <div class="direction_alternate-reverse">alternate-reverse</div> </body> </html>
[ 같은 애니메이션 결과를 출력하는 속성값 ] animation-timing-function: ease; /* 기본값 */ animation-timing-function: cubic-bazier(0,25, 0.1, 0.25, 1); animation-timing-function: linear; animation-timing-function: cubic-bazier(0,0,1,1); animation-timing-function: ease-in; animation-timing-function: cubic-bazier(0.42,0,1,1); animation-timing-function: ease-out; animation-timing-function: cubic-bazier(0,0,0.58,1); animation-timing-function: ease-in-out; animation-timing-function: cubic-bazier(0.42,0,0.58,1);
animation-delay: 0; /* 바로 재생 */ animation-delay: now; /* 바로 재생 */ animation-delay: 1.5s; /* 지연 재생 */ animation-delay: -500ms; /* 지정 시간 이후 프레임부터 바로 재생 */
<!DOCTYPE html> <html> <head> <style> @keyframes yoon { from {transform: translate(100px, 0)} to {transform: translate(300px, 0);}} div { position: absolute; margin: 50px; width: 100px; height: 100px; background-color: #ff0000; /* 애니메이션 속성 */ animation-name: yoon; animation-duration: 1s; animation-iteration-count: 1;} /* [ 바로 재생 ] */ .delay_0 { top: 20px; animation-delay: 0;} /* [ 바로 재생 ] */ .delay_now { top: 130px; animation-delay: now;} /* [ 지연 재생 ] */ .delay_plus { top: 240px; animation-delay: 1.5s;} /* [ 지정 시간 이후 프레임부터 바로 재생 ] */ .delay_minus { top: 350px; animation-delay: -500ms;} </style> </head> <body> <div class="delay_0">0</div> <div class="delay_now">now</div> <div class="delay_plus">plus</div> <div class="delay_minus">minus</div> </body> </html>
<!DOCTYPE html> <html> <head> <script type="text/javascript" src="http://code.jquery.com/jquery-3.2.0.min.js"></script> <script type="text/javascript"> $(function () { $('.play').click(function () { $('div').css('animation-play-state', 'running'); }); $('.stop').click(function () { $('div').css('animation-play-state', 'paused'); }); }); </script> <style> @keyframes yoon { from { transform: translate(100px, 0); } to { transform: translate(300px, 0); } } div { position: absolute; margin: 50px; width: 100px; height: 100px; background-color: #ff0000; /* 애니메이션 속성 */ animation-name: yoon; animation-duration: 2s; animation-iteration-count: infinite; } </style> </head> <body> <button class="play">running</button> <button class="stop">paused</button> <div>box</div> </body> </html>
<!DOCTYPE html> <html> <head> <style> @keyframes hojun { from{ background-position: 0px, 0px; } to { background-position: -3730px, 0; } } .building1{ width: 110px; height: 180px; background: url(./animation/building/building1.png) no-repeat; animation: hojun 5s steps(33); animation-fill-mode: forwards; } </style> </head> <body> <div class="building1"></div> </body> </html>
<!DOCTYPE html> <html> <head> <style> @keyframes one { from{ background-position: 0px, 0px; } to { background-position: -3730px, 0; } } @keyframes two { from{ background-position: 0px, 0px; } to { background-position: -5346px, 0; } } .building1{ width: 110px; height: 180px; background: url(./animation/building/building1.png) no-repeat; animation: one 5s steps(33); animation-fill-mode: forwards; } .building2{ width: 165px; height: 180px; background: url(./animation/building/building2.png) no-repeat; animation: two 5s steps(32); animation-fill-mode: forwards; transform: translate(300px, 300px); } </style> </head> <body> <div class="building1"></div> <div class="building2"></div> </body> </html>
<div class="back"> <div class="cat"></div> <div class="building1"></div> <div class="building2"></div> <div class="building7"></div> </div>
@keyframes cat { 33.3% { transform: translateY(0) scale(0.3); background: url(./animation/cat/1.png) no-repeat; } 66.6% { transform: translateX(-100px) scale(0.3); background: url(./animation/cat/2.png) no-repeat; } 100% { transform: translateX(-200px) scale(0.3); background: url(./animation/cat/3.png) no-repeat; } }
<!DOCTYPE html> <html> <head> <style> body, img, div{ padding: 0; margin: 0; } @keyframes one { from{ background-position: 0px, 0px; } to { background-position: -3730px, 0; } } @keyframes two { from{ background-position: 0px, 0px; } to { background-position: -5346px, 0; } } @keyframes seven { from{ background-position: 0px, 0px; } to { background-position: -3300px, 0; } } @keyframes cat { 33.3% { transform: translateY(0) scale(0.3); background: url(./animation/cat/1.png) no-repeat; } 66.6% { transform: translateX(-100px) scale(0.3); background: url(./animation/cat/2.png) no-repeat; } 100% { transform: translateX(-200px) scale(0.3); background: url(./animation/cat/3.png) no-repeat; } } .building1{ width: 110px; height: 180px; background: url(./animation/building/building1.png) no-repeat; animation: one 5s steps(33); animation-fill-mode: forwards; transform: scale(0.5); position: absolute; top: 10px; left: 10px; } .building2{ width: 165px; height: 180px; background: url(./animation/building/building2.png) no-repeat; animation: two 5s steps(32); animation-fill-mode: forwards; transform: scale(0.5); position: absolute; top: 300px; left: 300px; } .building7{ width: 150px; height: 180px; background: url(./animation/building/building7.png) no-repeat; animation: seven 5s steps(22); animation-fill-mode: forwards; position: absolute; top: 170px; left: 150px; } .cat{ width: 313px; height: 436px; background: url(./animation/cat.png) no-repeat; animation: cat .5s infinite; transform: scale(0.3); position: absolute; top: 10px; left: 600px; } .back{ width: 100vw; height: 150vh; background-color: YellowGreen; position: relative; } </style> </head> <body> <div class="back"> <div class="cat"></div> <div class="building1"></div> <div class="building2"></div> <div class="building7"></div> </div> </body> </html>
<!DOCTYPE html> <html> <head> <style> body, img, div{ padding: 0; margin: 0; } @keyframes one { from{ background-position: 0px, 0px; } to { background-position: -3730px, 0; } } @keyframes two { from{ background-position: 0px, 0px; } to { background-position: -5346px, 0; } } @keyframes seven { from{ background-position: 0px, 0px; } to { background-position: -3300px, 0; } } @keyframes cat { 10% { transform: translateY(0) scale(0.3); background: url(./animation/cat/1.png) no-repeat; } 20% { transform: translateX(-20px) scale(0.3); background: url(./animation/cat/2.png) no-repeat; } 30% { transform: translateX(-40px) scale(0.3); background: url(./animation/cat/3.png) no-repeat; } 40% { transform: translateX(-60px) scale(0.3); background: url(./animation/cat/1.png) no-repeat; } 50% { transform: translateX(-80px) scale(0.3); background: url(./animation/cat/2.png) no-repeat; } 60% { transform: translateX(-100px) scale(0.3); background: url(./animation/cat/3.png) no-repeat; } 70% { transform: translateX(-120px) scale(0.3); background: url(./animation/cat/1.png) no-repeat; } 80% { transform: translateX(-140px) scale(0.3); background: url(./animation/cat/2.png) no-repeat; } 90% { transform: translateX(-160px) scale(0.3); background: url(./animation/cat/3.png) no-repeat; } 100% { transform: translateX(-180px) scale(0.3); background: url(./animation/cat/1.png) no-repeat; } } .building1{ width: 110px; height: 180px; background: url(./animation/building/building1.png) no-repeat; animation: one 5s steps(33); animation-fill-mode: forwards; transform: scale(0.5); position: absolute; top: 10px; left: 10px; } .building2{ width: 165px; height: 180px; background: url(./animation/building/building2.png) no-repeat; animation: two 5s steps(32); animation-fill-mode: forwards; transform: scale(0.5); position: absolute; top: 300px; left: 300px; } .building7{ width: 150px; height: 180px; background: url(./animation/building/building7.png) no-repeat; animation: seven 5s steps(22); animation-fill-mode: forwards; position: absolute; top: 170px; left: 150px; } .cat{ width: 313px; height: 436px; background: url(./animation/cat.png) no-repeat; animation: cat 3s linear; animation-fill-mode: forwards; transform: scale(0.3); position: absolute; top: 10px; left: 600px; } .back{ width: 100vw; height: 150vh; background-color: YellowGreen; position: relative; } </style> </head> <body> <div class="back"> <div class="cat"></div> <div class="building1"></div> <div class="building2"></div> <div class="building7"></div> </div> </body> </html>