GSAP 사례 연구
Keyframes
배경 · MIT (local companion); GSAP Standard No Charge License (runtime)
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.
SOURCE FILES
독립 예제 코드 읽기
사례의 구도나 문서의 원리를 바탕으로 새로 만든 예제입니다. 아래 코드를 복사해 직접 응용할 수 있습니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.
vendor/gsap-previews/sources/gsap-fd69f1564d6e/study-brief.json실행 안내·자료
{
"id": "gsap-fd69f1564d6e",
"sourceId": "gsap",
"title": "Keyframes",
"sourceUrl": "https://demos.gsap.com/demo/keyframes",
"demoUrl": "https://codepen.io/GreenSock/pen/mdMYrBM",
"sourceStatus": "unresolved",
"sourceReason": "Public page unavailable through web browsing",
"sourceRevision": null,
"sourceLicense": null,
"originalDemoAcquired": false,
"disposition": "ready_for_independent_study",
"evidenceBasis": "catalog_explanation_and_linked_api",
"studyId": "gsap-fd69f1564d6e",
"sharedStudyCanonicalId": null,
"label": "Independently authored documentation study; original Pen unavailable",
"catalogEvidence": {
"localPath": "artifacts/full-import/gsap/remaining-docs/catalog-evidence.json",
"sha256": "4f2a4f9aa64456b85b905b562b60d7733629b41026787b3057cba29e8b10de69",
"bytes": 30117,
"recordId": "gsap-fd69f1564d6e"
},
"sourceInputs": [
{
"kind": "authored-study-specification",
"localPath": "artifacts/full-import/gsap/remaining-docs/study-specs.json",
"sha256": "cbb89331eda8783b5cf2f173b326b9195eeabfb223374fb60019161d9f46d4a6",
"bytes": 52279,
"recordId": "gsap-fd69f1564d6e"
},
{
"kind": "official-catalog-metadata",
"localPath": "artifacts/full-import/gsap/remaining-docs/catalog-evidence.json",
"sha256": "4f2a4f9aa64456b85b905b562b60d7733629b41026787b3057cba29e8b10de69",
"bytes": 30117,
"recordId": "gsap-fd69f1564d6e"
},
{
"kind": "official-documentation-evidence",
"localPath": "artifacts/full-import/gsap/remaining-docs/official-documentation-evidence.json",
"sha256": "bfa25f561e61c597569cf1bc0b3538e6dce4b9985a932775ceab41e665ca24a2",
"bytes": 11430,
"recordIds": [
"keyframes",
"tween"
]
},
{
"kind": "original-library-source",
"localPath": "artifacts/full-import/gsap/library/src/gsap-core.js",
"sha256": "8063816e0e7c3e6c07970fb06feafb06146fd266db5d8346419176b3d496e284",
"gitBlob": "c673b9e2e0ee04832afb13cb892ae75f503e268c",
"revision": "13e2b790546426a1a2e0e9b409f3f8dc6d6611f2",
"sourcePath": "src/gsap-core.js",
"ranges": [
{
"id": "direction-controls",
"startLine": 1354,
"endLine": 1391,
"rangeSha256": "8e2f36127b2fc568686d2d934ad3b74a21274c78bf1045d210e376260598819f"
},
{
"id": "keyframe-sequence",
"startLine": 2372,
"endLine": 2400,
"rangeSha256": "497cf7eb83906db82ce09e4987b7ffe094e5760292539d673dff4d3fa653f553"
},
{
"id": "tween-factories",
"startLine": 2637,
"endLine": 2657,
"rangeSha256": "23ae9d51865c9b7966bbc3b6cdbdb560fc790ff19e3b8bb524a3493fbf60c58a"
}
]
}
],
"studiedPrinciple": "Express a sequence of movements on one target as an array of keyframe vars with explicit step durations.",
"namedApis": [
{
"name": "gsap.to(target, {keyframes: [step1, step2, step3]})",
"purpose": "Create sequential per-step to tweens inside one keyframe animation.",
"libraryEvidence": [
"keyframe-sequence",
"tween-factories"
],
"documentationEvidence": [
"keyframes",
"tween"
]
},
{
"name": "tween.play(), tween.pause(), tween.restart()",
"purpose": "Control the created sequence without rebuilding on each play press.",
"libraryEvidence": [
"direction-controls"
],
"documentationEvidence": [
"tween"
]
}
],
"implementationBrief": {
"scene": "One authored square in a bounded stage, with three labeled destination markers and a readable display of the study's keyframe array.",
"controls": [
{
"label": "Play / Pause",
"type": "button",
"action": "Control the existing keyframe tween."
},
{
"label": "Restart",
"type": "button",
"action": "Return to the declared initial state and play the same sequence."
},
{
"label": "Progress",
"type": "range",
"action": "Pause and seek through normalized progress to inspect step boundaries."
}
],
"mechanismSteps": [
"Set the target's initial x, y, rotation, and scale explicitly once; record those values separately from keyframe destinations.",
"Create a paused gsap.to with a keyframes array. Each step contains destination properties, duration, and ease; array order defines the sequence.",
"Use cumulative step durations to label the current segment. Do not infer segment number by multiplying progress by the number of steps when durations differ.",
"Restart from the initial state and expose the exact newly authored array as study code.",
"If the stage must shrink, rebuild its authored coordinate plan at a stable paused state and identify that responsive choice as study behavior."
]
},
"authoredDefaults": {
"initial": {
"x": 0,
"y": 0,
"rotation": 0,
"scale": 1
},
"keyframeSteps": [
{
"x": 120,
"y": 0,
"duration": 0.6,
"ease": "power1.inOut"
},
{
"x": 120,
"y": 80,
"rotation": 90,
"duration": 0.4,
"ease": "power1.inOut"
},
{
"x": 0,
"y": 0,
"rotation": 180,
"duration": 0.8,
"ease": "power1.inOut"
}
],
"targetSizePixels": 28
},
"verificationRequired": [
"The target visits the declared destinations in array order and total duration equals the sum of step durations.",
"Progress jumps to 0, the first boundary, the second boundary, and 1 produce the expected coordinates.",
"Pause and restart remain repeatable without accumulating duplicate tweens.",
"All markers and the static step list remain readable at 320px."
],
"unsupportedClaims": [
"Original composition, assets, timing, breakpoint behavior, and live interaction are not observable in captured frames. This brief does not establish visual fidelity or recover original code.",
"The official description recommends keyframes but provides no original path, step values, duration, or marker layout. The numeric sequence is newly authored."
],
"dependencies": {
"package": "gsap",
"version": "3.15.0",
"revision": "13e2b790546426a1a2e0e9b409f3f8dc6d6611f2",
"repository": "https://github.com/greensock/GSAP",
"plugins": [],
"registration": "Core includes CSSPlugin; no separate plugin registration required.",
"runtimeCandidates": [
{
"path": "dist/gsap.js",
"localPath": "artifacts/full-import/gsap/library/dist/gsap.js",
"gitBlob": "ec5ffee3f099bc454f2f1c79cee6a55ba97e7400",
"sha256": "01effe0e121c22ba0f885a3f091f3dc6d1f0d445c4fba37dcb298e314eca5cfb",
"bytes": 179557
}
],
"license": {
"name": "GSAP Standard 'No Charge' License (custom, not MIT)",
"localPath": "artifacts/full-import/gsap/licenses/gsap-standard-license.html",
"sha256": "bf8536e67b77db780f7db0c14a645f184f554bb19e938d1e52d58986e2f10528",
"bytes": 6988,
"url": "https://gsap.com/community/standard-license/"
},
"originalDependencyVersionKnown": false
},
"runtimeVerified": false,
"implementationWritten": false
}
vendor/gsap-previews/sources/gsap-fd69f1564d6e/source-inputs.json실행 안내·자료
[
{
"kind": "authored-study-specification",
"localPath": "artifacts/full-import/gsap/remaining-docs/study-specs.json",
"sha256": "cbb89331eda8783b5cf2f173b326b9195eeabfb223374fb60019161d9f46d4a6",
"bytes": 52279,
"recordId": "gsap-fd69f1564d6e"
},
{
"kind": "official-catalog-metadata",
"localPath": "artifacts/full-import/gsap/remaining-docs/catalog-evidence.json",
"sha256": "4f2a4f9aa64456b85b905b562b60d7733629b41026787b3057cba29e8b10de69",
"bytes": 30117,
"recordId": "gsap-fd69f1564d6e"
},
{
"kind": "official-documentation-evidence",
"localPath": "artifacts/full-import/gsap/remaining-docs/official-documentation-evidence.json",
"sha256": "bfa25f561e61c597569cf1bc0b3538e6dce4b9985a932775ceab41e665ca24a2",
"bytes": 11430,
"recordIds": [
"keyframes",
"tween"
]
},
{
"kind": "original-library-source",
"localPath": "artifacts/full-import/gsap/library/src/gsap-core.js",
"sha256": "8063816e0e7c3e6c07970fb06feafb06146fd266db5d8346419176b3d496e284",
"gitBlob": "c673b9e2e0ee04832afb13cb892ae75f503e268c",
"revision": "13e2b790546426a1a2e0e9b409f3f8dc6d6611f2",
"sourcePath": "src/gsap-core.js",
"ranges": [
{
"id": "direction-controls",
"startLine": 1354,
"endLine": 1391,
"rangeSha256": "8e2f36127b2fc568686d2d934ad3b74a21274c78bf1045d210e376260598819f"
},
{
"id": "keyframe-sequence",
"startLine": 2372,
"endLine": 2400,
"rangeSha256": "497cf7eb83906db82ce09e4987b7ffe094e5760292539d673dff4d3fa653f553"
},
{
"id": "tween-factories",
"startLine": 2637,
"endLine": 2657,
"rangeSha256": "23ae9d51865c9b7966bbc3b6cdbdb560fc790ff19e3b8bb524a3493fbf60c58a"
}
]
}
]
함께 쓰는 파일 4개 보기
vendor/gsap-previews/sources/gsap-fd69f1564d6e/index.html실행 안내·자료
<main class="study"><p class="study-label">공식 API 기반 · 독립 예제</p><header><h2>시간 길이가 다른 세 단계</h2><p>배열의 순서가 이동 순서가 됩니다. 각 구간의 길이를 합쳐 현재 단계를 판별합니다.</p></header><div class="stage"><div class="key-world"><svg viewBox="0 0 240 170" aria-hidden="true"><path d="M44 34H164V114L44 34" fill="none" stroke="#617394" stroke-dasharray="4 6"/><g fill="#aeb7c9" font-family="system-ui" font-size="11"><text x="15" y="15">시작 / 1.8s</text><text x="151" y="15">0.6s</text><text x="151" y="147">1.0s</text></g></svg><div id="key-mark"></div></div></div><div class="controls"><button id="play" type="button">재생 / 정지</button><button id="restart" type="button">다시 시작</button><label>진행<input id="progress" type="range" min="0" max="100" value="0" step="1"></label><label><input id="static-mode" type="checkbox">정적 보기</label></div><p class="readout" id="reading" aria-live="off"></p><pre class="small-code">0.6s → x:120, y:0
0.4s → x:120, y:80, rotation:90
0.8s → x:0, y:0, rotation:180</pre></main>vendor/gsap-previews/sources/gsap-fd69f1564d6e/style.css실행 안내·자료
*{box-sizing:border-box}body{background:#111522;color:#f5f2eb;font-family:system-ui,sans-serif;margin:0}button{font:inherit}.study{margin:0 auto;max-width:1100px;min-height:100vh;padding:clamp(20px,4vw,42px)}.study-label{color:#95efd2;font-size:12px;letter-spacing:.08em;margin:0 0 24px}header h2{font-size:clamp(24px,4vw,40px);letter-spacing:-.04em;margin:0 0 12px}header p{color:#aeb7c9;line-height:1.6;margin:0 0 24px;max-width:62ch}.controls{display:flex;flex-wrap:wrap;gap:8px;margin:20px 0}button{background:#26314b;border:1px solid #617394;border-radius:9px;color:#f5f2eb;cursor:pointer;padding:9px 14px}button:hover{background:#334261}button:focus-visible{outline:3px solid #95efd2;outline-offset:3px}button:disabled{cursor:default;opacity:.45}.stage{align-items:center;background:radial-gradient(ellipse at top,#243253,#141c2d);border:1px solid #3d4c68;border-radius:20px;display:flex;justify-content:center;min-height:220px;overflow:hidden;position:relative}.note{color:#aeb7c9;font-size:13px;line-height:1.5}.readout{color:#95efd2;font-family:ui-monospace,monospace;font-size:13px;min-height:2em}.stage svg{display:block;max-width:100%}code{font-family:ui-monospace,monospace}button[aria-pressed="true"]{background:#95efd2;color:#111522}
.study{padding:18px;max-width:960px}.study-label{margin-bottom:10px}header h2{font-size:clamp(22px,4vw,32px);margin-bottom:8px}header p{font-size:14px;margin-bottom:16px}.controls{align-items:center;gap:9px;margin:14px 0}.controls label{align-items:center;color:#cbd4e3;display:flex;flex-wrap:wrap;font-size:13px;gap:7px}.controls input[type=range]{accent-color:#95efd2;max-width:145px}.controls select{background:#26314b;border:1px solid #617394;border-radius:7px;color:#f5f2eb;font:inherit;padding:6px}.stage{min-height:180px}.readout{line-height:1.5;margin:8px 0}.small-code{background:#172036;border:1px solid #354258;border-radius:10px;color:#c3d1e7;font-size:12px;line-height:1.5;max-height:180px;overflow:auto;padding:12px;white-space:pre-wrap;word-break:break-word}.pair{display:flex;gap:12px;justify-content:center}.pair figure{flex:1;margin:0;max-width:300px;min-width:0;padding:8px}.pair figcaption{color:#aeb7c9;font-family:ui-monospace,monospace;font-size:12px;margin-bottom:6px;text-align:center}.pair svg{display:block;max-height:210px;width:100%}.tile-art{align-items:center;border-radius:14px;color:#152039;display:flex;justify-content:center}.tile-art svg{height:65%;width:65%}.menu-stage{min-height:196px;padding:18px}.menu-panels{display:flex;gap:10px;max-width:570px;width:100%}.menu-panel{align-items:center;background:linear-gradient(135deg,#95efd2,#75d8ed);border-radius:14px;color:#142137;display:flex;flex:1;flex-direction:column;font-weight:650;gap:10px;justify-content:center;min-height:135px;min-width:0;padding:12px}.menu-panel:nth-child(2){background:linear-gradient(135deg,#fbadce,#897dff)}.menu-panel:nth-child(3){background:linear-gradient(135deg,#ffd28c,#fc7f8e)}.menu-panel svg{height:58px;width:58px}.icon-bars{display:inline-flex;flex-direction:column;gap:5px;margin-right:7px;vertical-align:middle}.icon-bars span{background:currentColor;display:block;height:2px;width:17px}.endpoint-note{color:#aeb7c9;font-size:12px;line-height:1.5}button[hidden],[hidden]{display:none!important}@media(max-width:380px){.study{padding:14px}.controls label{max-width:100%}.menu-panels{gap:6px}.menu-panel{font-size:12px;padding:7px}.pair{gap:0}}
.key-world{height:170px;position:relative;width:240px}.key-world svg{height:170px;width:240px}#key-mark{background:linear-gradient(135deg,#95efd2,#897dff);border-radius:8px;height:28px;left:30px;position:absolute;top:20px;width:28px}vendor/gsap-previews/sources/gsap-fd69f1564d6e/script.js실행 안내·자료
gsap.registerPlugin();
const q=selector=>document.querySelector(selector),qa=selector=>Array.from(document.querySelectorAll(selector));
const studyAbort=new AbortController(),studyCleanup=[];
const listen=(target,type,handler)=>target.addEventListener(type,handler,{signal:studyAbort.signal});
const preference=matchMedia('(prefers-reduced-motion: reduce)'),staticControl=q('#static-mode');
staticControl.checked=preference.matches;staticControl.disabled=preference.matches;
const isStatic=()=>staticControl.checked||preference.matches;
let applyMode=()=>{};
listen(staticControl,'change',()=>applyMode());
listen(preference,'change',()=>{staticControl.checked=preference.matches;staticControl.disabled=preference.matches;applyMode();});
listen(window,'pagehide',()=>{studyCleanup.forEach(cleanup=>cleanup());studyAbort.abort();});
const reading=text=>q('#reading').textContent=text;
const KEYFRAMES=[{x:120,y:0,duration:.6,ease:'power1.inOut'},{x:120,y:80,rotation:90,duration:.4,ease:'power1.inOut'},{x:0,y:0,rotation:180,duration:.8,ease:'power1.inOut'}];let keyTween=null;
function inspect(){if(!keyTween)return;const time=keyTween.time();let elapsed=0,segment=KEYFRAMES.length;for(let i=0;i<KEYFRAMES.length;i++){elapsed+=KEYFRAMES[i].duration;if(time<elapsed-.00001){segment=i+1;break;}}q('#progress').value=Math.round(keyTween.progress()*100);reading(time.toFixed(2)+' / '+keyTween.duration().toFixed(2)+'초 · '+segment+'단계');}
gsap.set('#key-mark',{x:0,y:0,rotation:0,scale:1});keyTween=gsap.to('#key-mark',{paused:true,keyframes:KEYFRAMES,onUpdate:inspect});
listen(q('#play'),'click',()=>{if(isStatic())keyTween.pause().progress(1,true);else keyTween.paused(!keyTween.paused());inspect();});listen(q('#restart'),'click',()=>{if(isStatic())keyTween.pause().progress(0,true);else keyTween.restart();inspect();});listen(q('#progress'),'input',e=>{keyTween.pause().progress(Number(e.target.value)/100,true);inspect();});applyMode=()=>{keyTween.pause().progress(0,true);inspect();};studyCleanup.push(()=>keyTween.kill());applyMode();LICENSE실행 안내·자료
Locally authored companion HTML/CSS/control wiring: MIT License.
Copyright (c) 2026 StyleGallery contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The typed source-inputs.json and study-brief.json preserve official catalog, documentation and original library source pointers. This is independently authored API research, not a recovered CodePen source or documentation snippet. The local MIT notice does not assign new licenses to referenced third-party sources.
GSAP 3.15.0: Copyright 2026, GreenSock. All rights reserved. Subject to https://gsap.com/standard-license. Original library notices are preserved.
BEHIND THE EXAMPLE
이 장면을 만드는 원리
순서와 누적 시간이 다른 키프레임
진행률과 단계 인덱스를 혼동하지 않고 각 단계의 실제 지속 시간을 합쳐 구간을 판별합니다.
- 이 예제에서는
- 새로 정한 세 keyframe의 지속 시간은 0.6·0.4·0.8초입니다. 한 트윈이 배열을 순서대로 실행하고 inspect는 time()을 누적 경계와 비교합니다. 총 진행률을 단순히 세 등분하지 않습니다. 세 도착 마커와 입력 값은 독립 예제가 공개하는 좌표 계약입니다.
