GSAP 사례 연구
Toggle Direction
배경 · MIT (local companion); GSAP Standard No Charge License (runtime)
갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.
저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.
SOURCE FILES
독립 예제 코드 읽기
사례의 구도나 문서의 원리를 바탕으로 새로 만든 예제입니다. 아래 코드를 복사해 직접 응용할 수 있습니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.
vendor/gsap-previews/sources/gsap-0d9ff9f86ee3/study-brief.json실행 안내·자료
{
"id": "gsap-0d9ff9f86ee3",
"sourceId": "gsap",
"title": "Toggle Direction",
"sourceUrl": "https://demos.gsap.com/demo/toggle-direction",
"demoUrl": "https://codepen.io/GreenSock/pen/yyByMar",
"sourceStatus": "unresolved",
"sourceReason": "Public page unavailable through web browsing",
"sourceRevision": null,
"sourceLicense": null,
"originalDemoAcquired": false,
"disposition": "ready_for_independent_study",
"evidenceBasis": "catalog_linked_api",
"studyId": "gsap-0d9ff9f86ee3",
"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-0d9ff9f86ee3"
},
"sourceInputs": [
{
"kind": "authored-study-specification",
"localPath": "artifacts/full-import/gsap/remaining-docs/study-specs.json",
"sha256": "cbb89331eda8783b5cf2f173b326b9195eeabfb223374fb60019161d9f46d4a6",
"bytes": 52279,
"recordId": "gsap-0d9ff9f86ee3"
},
{
"kind": "official-catalog-metadata",
"localPath": "artifacts/full-import/gsap/remaining-docs/catalog-evidence.json",
"sha256": "4f2a4f9aa64456b85b905b562b60d7733629b41026787b3057cba29e8b10de69",
"bytes": 30117,
"recordId": "gsap-0d9ff9f86ee3"
},
{
"kind": "official-documentation-evidence",
"localPath": "artifacts/full-import/gsap/remaining-docs/official-documentation-evidence.json",
"sha256": "bfa25f561e61c597569cf1bc0b3538e6dce4b9985a932775ceab41e665ca24a2",
"bytes": 11430,
"recordIds": [
"reversed"
]
},
{
"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"
}
]
}
],
"studiedPrinciple": "Toggle one existing tween's playback direction using its reversed getter/setter while handling paused state explicitly.",
"namedApis": [
{
"name": "tween.reversed(!tween.reversed())",
"purpose": "Invert the existing animation's direction without creating a new tween.",
"libraryEvidence": [
"direction-controls"
],
"documentationEvidence": [
"reversed"
]
},
{
"name": "tween.resume()",
"purpose": "Unpause after setting direction; reversed alone does not start a paused animation.",
"libraryEvidence": [
"direction-controls"
],
"documentationEvidence": [
"reversed"
]
}
],
"implementationBrief": {
"scene": "One authored marker traveling along a labeled start/end track. Show current direction, paused state, and normalized progress next to native controls.",
"controls": [
{
"label": "Toggle direction",
"type": "button",
"action": "Set reversed to its inverse, then resume the same tween."
},
{
"label": "Pause / Resume",
"type": "button",
"action": "Change paused state without changing direction."
},
{
"label": "Progress",
"type": "range",
"action": "Pause and seek to a selected progress; the next direction toggle must resume from that point."
},
{
"label": "Reset",
"type": "button",
"action": "Restore progress zero, reversed true, and paused true so the next toggle runs forward."
}
],
"mechanismSteps": [
"Create one paused tween from a known starting x to an authored destination. Initialize reversed(true) while progress remains zero and paused.",
"The first Toggle direction sets reversed(false) and calls resume(), so movement begins forward from zero.",
"Later toggles call reversed(!reversed()).resume() on the same instance. Do not restart or rebuild on each click, because that would hide mid-flight reversal.",
"Use direct progress seeking for inspection and keep it separate from direction state; display both values so a paused reversed tween is understandable.",
"At either endpoint a direction pointing outside the playable range cannot travel farther. The next toggle points inward and resumes movement; explain this as endpoint behavior."
]
},
"authoredDefaults": {
"startX": 0,
"endX": 160,
"markerSizePixels": 28,
"durationSeconds": 1.2,
"ease": "power1.inOut",
"initialProgress": 0,
"initialReversed": true,
"initialPaused": true
},
"verificationRequired": [
"The first toggle moves forward; the second toggle midway returns toward the start without a position reset.",
"One tween instance is retained across repeated toggles.",
"After pausing and seeking to 0.5, toggling both changes direction and resumes from the current position.",
"Reset recreates the declared progress/direction/paused state, and endpoint toggles move inward correctly.",
"All controls work with keyboard input; static mode provides labeled endpoints without continuous animation."
],
"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 catalog supplies the reversed() API link but no original scene, endpoints, duration, or control implementation. All those details are newly authored.",
"Setting reversed does not itself unpause a paused tween; the explicit resume call is required by this study's interaction contract."
],
"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-0d9ff9f86ee3/source-inputs.json실행 안내·자료
[
{
"kind": "authored-study-specification",
"localPath": "artifacts/full-import/gsap/remaining-docs/study-specs.json",
"sha256": "cbb89331eda8783b5cf2f173b326b9195eeabfb223374fb60019161d9f46d4a6",
"bytes": 52279,
"recordId": "gsap-0d9ff9f86ee3"
},
{
"kind": "official-catalog-metadata",
"localPath": "artifacts/full-import/gsap/remaining-docs/catalog-evidence.json",
"sha256": "4f2a4f9aa64456b85b905b562b60d7733629b41026787b3057cba29e8b10de69",
"bytes": 30117,
"recordId": "gsap-0d9ff9f86ee3"
},
{
"kind": "official-documentation-evidence",
"localPath": "artifacts/full-import/gsap/remaining-docs/official-documentation-evidence.json",
"sha256": "bfa25f561e61c597569cf1bc0b3538e6dce4b9985a932775ceab41e665ca24a2",
"bytes": 11430,
"recordIds": [
"reversed"
]
},
{
"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"
}
]
}
]
함께 쓰는 파일 4개 보기
vendor/gsap-previews/sources/gsap-0d9ff9f86ee3/index.html실행 안내·자료
<main class="study"><p class="study-label">공식 API 기반 · 독립 예제</p><header><h2>방향과 정지는 다른 상태</h2><p>같은 효과의 방향을 뒤집은 다음 재개합니다. 매 클릭마다 처음부터 만들지 않습니다.</p></header><div class="stage"><div class="direction-track"><span class="direction-label start">시작 · 0</span><span class="direction-label end">끝 · 160</span><div id="direction-mark"><svg viewBox="0 0 120 120" aria-hidden="true"><path d="m60 7 15 31 34 5-25 24 6 34-30-16-30 16 6-34L11 43l34-5Z" fill="currentColor"/></svg></div></div></div><div class="controls"><button id="toggle" type="button">방향 바꾸기</button><button id="pause" type="button">정지 / 재개</button><button id="reset" 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></main>vendor/gsap-previews/sources/gsap-0d9ff9f86ee3/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}}
.direction-track{border-top:1px dashed #617394;height:70px;position:relative;width:188px}#direction-mark{color:#95efd2;height:28px;left:0;position:absolute;top:16px;width:28px}#direction-mark svg{height:100%;width:100%}.direction-label{color:#aeb7c9;font-size:11px;position:absolute;top:55px}.direction-label.start{left:0}.direction-label.end{left:144px;white-space:nowrap}vendor/gsap-previews/sources/gsap-0d9ff9f86ee3/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;
let directionTween=null;
function inspect(){if(!directionTween)return;q('#progress').value=Math.round(directionTween.progress()*100);reading('방향: '+(directionTween.reversed()?'시작 쪽':'끝 쪽')+' · '+(directionTween.paused()?'정지':'재생')+' · '+q('#progress').value+'%');}
gsap.set('#direction-mark',{x:0});directionTween=gsap.to('#direction-mark',{x:160,duration:1.2,ease:'power1.inOut',paused:true,onUpdate:inspect});
function reset(){directionTween.pause().progress(0,true).reversed(true);inspect();}
listen(q('#toggle'),'click',()=>{directionTween.reversed(!directionTween.reversed());if(isStatic())directionTween.pause().progress(directionTween.reversed()?0:1,true);else directionTween.resume();inspect();});listen(q('#pause'),'click',()=>{if(!isStatic())directionTween.paused(!directionTween.paused());inspect();});listen(q('#reset'),'click',reset);listen(q('#progress'),'input',e=>{directionTween.pause().progress(Number(e.target.value)/100,true);inspect();});applyMode=reset;studyCleanup.push(()=>directionTween.kill());reset();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
이 장면을 만드는 원리
방향 전환 뒤 명시적으로 재개
재생 방향, 진행 위치와 정지 상태를 서로 독립적인 입력으로 다룹니다.
- 이 예제에서는
- 한 트윈을 유지하며 reversed 값을 반대로 바꾼 뒤 resume합니다. 진행 슬라이더는 방향을 바꾸지 않고 현재 위치에서 정지합니다. reset은 진행 0·정지·역방향 플래그로 시작하여 첫 방향 바꾸기가 끝 쪽으로 움직이게 합니다. 정적 모드는 같은 방향 입력의 끝점으로 즉시 이동합니다.
코드와 함께 확인하기
