GSAP 사례 연구

Interruptible Single Timeline Enter/Exit

탐색 · MIT (local companion); GSAP Standard No Charge License (runtime)

탐색 더 보기
CASE STUDY
Interruptible Single Timeline Enter/Exit 정적 미리보기

갤러리 안에서는 화면을 벗어나거나 움직임을 멈추면 예제도 닫힙니다. 다시 재생할 때는 처음부터 시작해요.

큰 화면으로 보기 새 탭실행 HTML 저장

저장한 HTML에서는 갤러리의 재생 설정이 적용되지 않아요.

SOURCE FILES

독립 예제 코드 읽기

6개 파일

사례의 구도나 문서의 원리를 바탕으로 새로 만든 예제입니다. 아래 코드를 복사해 직접 응용할 수 있습니다. 실행 HTML에는 미리보기를 위한 실행 환경이 들어 있습니다.

vendor/gsap-previews/sources/gsap-f6be61ceefd2/study-brief.json실행 안내·자료
파일 저장

{
  "id": "gsap-f6be61ceefd2",
  "sourceId": "gsap",
  "title": "Interruptible Single Timeline Enter/Exit",
  "sourceUrl": "https://demos.gsap.com/demo/interruptible-single-timeline-enterexit",
  "demoUrl": "https://codepen.io/GreenSock/pen/qEaKJZr",
  "sourceStatus": "unresolved",
  "sourceReason": "Embedded public source payload not found in returned page",
  "sourceRevision": null,
  "sourceLicense": null,
  "originalDemoAcquired": false,
  "disposition": "ready_for_independent_study",
  "evidenceBasis": "catalog_explanation_and_linked_api",
  "studyId": "gsap-f6be61ceefd2",
  "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-f6be61ceefd2"
  },
  "sourceInputs": [
    {
      "kind": "authored-study-specification",
      "localPath": "artifacts/full-import/gsap/remaining-docs/study-specs.json",
      "sha256": "cbb89331eda8783b5cf2f173b326b9195eeabfb223374fb60019161d9f46d4a6",
      "bytes": 52279,
      "recordId": "gsap-f6be61ceefd2"
    },
    {
      "kind": "official-catalog-metadata",
      "localPath": "artifacts/full-import/gsap/remaining-docs/catalog-evidence.json",
      "sha256": "4f2a4f9aa64456b85b905b562b60d7733629b41026787b3057cba29e8b10de69",
      "bytes": 30117,
      "recordId": "gsap-f6be61ceefd2"
    },
    {
      "kind": "official-documentation-evidence",
      "localPath": "artifacts/full-import/gsap/remaining-docs/official-documentation-evidence.json",
      "sha256": "bfa25f561e61c597569cf1bc0b3538e6dce4b9985a932775ceab41e665ca24a2",
      "bytes": 11430,
      "recordIds": [
        "add-pause",
        "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": "timeline-pause",
          "startLine": 1785,
          "endLine": 1790,
          "rangeSha256": "6ccdb8b5fc2f0b6b507fe2864dcb4c552193e913f247377ca4f606fbe4714ebe"
        },
        {
          "id": "reverse-ease",
          "startLine": 2114,
          "endLine": 2136,
          "rangeSha256": "a94b1617e88c0c3328ce1d2038eaa0d44a03612cf5b33cbfeac7140245698555"
        }
      ]
    }
  ],
  "studiedPrinciple": "Use one timeline with an open-state pause so closing during entry reverses entry, while closing from the held-open state proceeds into a separately authored forward exit.",
  "namedApis": [
    {
      "name": "gsap.timeline().addPause('open', callback)",
      "purpose": "Separate the entrance and distinct exit in a single reusable timeline.",
      "libraryEvidence": [
        "timeline-pause"
      ],
      "documentationEvidence": [
        "add-pause"
      ]
    },
    {
      "name": "timeline.play(), timeline.reverse(), timeline.pause()",
      "purpose": "Route interaction according to whether the entrance is unfinished or the timeline is held at open.",
      "libraryEvidence": [
        "direction-controls"
      ],
      "documentationEvidence": [
        "add-pause"
      ]
    },
    {
      "name": "easeReverse: 'power3.in' on entrance tweens",
      "purpose": "Give reverse entrance motion a different response from the forward back.out entrance.",
      "libraryEvidence": [
        "reverse-ease"
      ],
      "documentationEvidence": [
        "tween"
      ],
      "minimumVersion": "3.15.0"
    }
  ],
  "implementationBrief": {
    "scene": "A native menu toggle and three independently styled panels containing short new labels. Show the current phase as closed, entering, held open, reversing entry, or exiting.",
    "controls": [
      {
        "label": "Open / Close",
        "type": "button",
        "action": "Change desiredOpen and route play/reverse using the phase contract below."
      },
      {
        "label": "Slow motion",
        "type": "checkbox",
        "action": "Use a lower absolute playback speed so the two closing routes can be inspected."
      },
      {
        "label": "Reset",
        "type": "button",
        "action": "Pause at zero with callbacks suppressed and restore all authored closed values."
      }
    ],
    "mechanismSteps": [
      "Build one paused timeline: explicit entrance fromTo tweens, then label open and addPause at the end of entrance, then separate to tweens that move panels downward with authored rotations.",
      "Opening from closed plays from zero. Before reaching the open pause, a close request calls reverse(); onReverseComplete marks closed.",
      "The pause callback marks held open. Closing from this held state calls play() from the same playhead so the timeline proceeds into the distinct exit instead of reversing entry.",
      "During reversing entry, a new open request calls play() from the current position. Do not create another timeline or reset the panel position.",
      "Choose an explicit authored rule for an open request during the distinct exit: queue it, finish the exit, restore the base closed state, then play from zero once. Show queued open in the phase display.",
      "After the forward exit completes, reset the same timeline to zero without firing state callbacks, restore closed styles, and honor the queued desired state. Ensure callback updates do not create recursion.",
      "Set easeReverse on the entrance child tweens, not only on the timeline object. The source description names back.out forward and power3.in reverse; all durations and geometry remain authored."
    ]
  },
  "authoredDefaults": {
    "panelCount": 3,
    "entranceDurationSeconds": 0.6,
    "entranceStaggerSeconds": 0.06,
    "entranceFromXPercent": -110,
    "entranceEase": "back.out(1.4)",
    "entranceReverseEase": "power3.in",
    "exitDurationSeconds": 0.45,
    "exitYPercent": 150,
    "exitRotationsDegrees": [
      -12,
      9,
      14
    ],
    "reopenDuringExit": "queue one reopen after exit completes"
  },
  "verificationRequired": [
    "Closing at 25% of entrance reverses that entrance rather than running the separate exit.",
    "Closing after the open pause runs the distinct downward/rotating exit.",
    "Reopening during reverse entry continues from the current position without a jump.",
    "Reopening during the distinct exit follows the declared queue policy and cannot leave multiple timelines running.",
    "Reset and reduced motion produce a readable stable menu state; keyboard focus never remains inside hidden content."
  ],
  "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 source description establishes the two closing routes and named eases, but original timing, panel count, geometry, and controls were not recovered.",
    "The exit rotations here are declared fixed study values; the original description's random distribution is not reproduced.",
    "The queue policy for reopening during exit is authored because that behavior is not described."
  ],
  "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-f6be61ceefd2/source-inputs.json실행 안내·자료
파일 저장

[
  {
    "kind": "authored-study-specification",
    "localPath": "artifacts/full-import/gsap/remaining-docs/study-specs.json",
    "sha256": "cbb89331eda8783b5cf2f173b326b9195eeabfb223374fb60019161d9f46d4a6",
    "bytes": 52279,
    "recordId": "gsap-f6be61ceefd2"
  },
  {
    "kind": "official-catalog-metadata",
    "localPath": "artifacts/full-import/gsap/remaining-docs/catalog-evidence.json",
    "sha256": "4f2a4f9aa64456b85b905b562b60d7733629b41026787b3057cba29e8b10de69",
    "bytes": 30117,
    "recordId": "gsap-f6be61ceefd2"
  },
  {
    "kind": "official-documentation-evidence",
    "localPath": "artifacts/full-import/gsap/remaining-docs/official-documentation-evidence.json",
    "sha256": "bfa25f561e61c597569cf1bc0b3538e6dce4b9985a932775ceab41e665ca24a2",
    "bytes": 11430,
    "recordIds": [
      "add-pause",
      "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": "timeline-pause",
        "startLine": 1785,
        "endLine": 1790,
        "rangeSha256": "6ccdb8b5fc2f0b6b507fe2864dcb4c552193e913f247377ca4f606fbe4714ebe"
      },
      {
        "id": "reverse-ease",
        "startLine": 2114,
        "endLine": 2136,
        "rangeSha256": "a94b1617e88c0c3328ce1d2038eaa0d44a03612cf5b33cbfeac7140245698555"
      }
    ]
  }
]
함께 쓰는 파일 4개 보기
vendor/gsap-previews/sources/gsap-f6be61ceefd2/index.html실행 안내·자료
파일 저장

<main class="study"><p class="study-label">공식 API 기반 · 독립 예제</p><header><h2>언제 닫느냐에 따른 두 경로</h2><p>등장 중 닫으면 되감습니다. 열린 지점에서 닫으면 아래쪽 퇴장으로 이어집니다.</p></header><div class="stage menu-stage"><div class="menu-panels"><div class="menu-panel"><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><span class="menu-label">Explore</span></div><div class="menu-panel"><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><span class="menu-label">Collect</span></div><div class="menu-panel"><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><span class="menu-label">Create</span></div></div></div><div class="controls"><button id="toggle" type="button">닫기</button><label><input id="slow" type="checkbox">천천히</label><button id="reset" type="button">초기화</button><label><input id="static-mode" type="checkbox">정적 보기</label></div><p class="readout" id="reading" aria-live="off"></p><p class="endpoint-note">별도 퇴장 중 다시 열면 퇴장을 마친 후 한 번 열립니다. 처음에는 열린 상태를 보여 줍니다.</p></main>
vendor/gsap-previews/sources/gsap-f6be61ceefd2/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}}

vendor/gsap-previews/sources/gsap-f6be61ceefd2/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 panels=qa('.menu-panel'),menu=q('.menu-panels'),toggle=q('#toggle');let desiredOpen=true,phase='held open',menuTimeline=null;
function reflect(){toggle.textContent=desiredOpen?'닫기':'열기';toggle.setAttribute('aria-expanded',String(desiredOpen));menu.setAttribute('aria-hidden',String(!desiredOpen));reading('상태: '+phase+(phase==='exiting'&&desiredOpen?' · 열기 1회 대기':''));}
function closeBase(){gsap.set(panels,{xPercent:-110,yPercent:0,rotation:0,autoAlpha:0});}
function finishExit(){const reopen=desiredOpen;menuTimeline.pause(0,true);closeBase();phase='closed';reflect();if(reopen&&!isStatic()){phase='entering';menuTimeline.play(0);reflect();}}
menuTimeline=gsap.timeline({paused:true,onComplete:finishExit,onReverseComplete:()=>{phase='closed';desiredOpen=false;reflect();}})
.fromTo(panels,{xPercent:-110,yPercent:0,rotation:0,autoAlpha:0},{xPercent:0,autoAlpha:1,duration:.6,stagger:.06,ease:'back.out(1.4)',easeReverse:'power3.in'})
.addLabel('open').addPause('open',()=>{phase='held open';reflect();if(!desiredOpen){phase='exiting';menuTimeline.play();reflect();}})
.to(panels,{yPercent:150,rotation:i=>[-12,9,14][i],autoAlpha:0,duration:.45,ease:'power2.in'},'open');
function requestToggle(){desiredOpen=!desiredOpen;
if(isStatic()){menuTimeline.pause();gsap.set(panels,{xPercent:0,yPercent:0,rotation:0,autoAlpha:desiredOpen?1:0});phase=desiredOpen?'held open':'closed';reflect();return;}
if(phase==='exiting'){reflect();return;}
if(desiredOpen){phase='entering';menuTimeline.play();}
else if(phase==='held open'){phase='exiting';menuTimeline.play();}
else {phase='reversing entry';menuTimeline.reverse();}reflect();}
function reset(){desiredOpen=false;phase='closed';menuTimeline.pause(0,true).reversed(false);closeBase();reflect();}
listen(toggle,'click',requestToggle);listen(q('#slow'),'change',e=>{const speed=e.target.checked?0.25:1;menuTimeline.timeScale(menuTimeline.reversed()?-speed:speed);});listen(q('#reset'),'click',reset);
applyMode=()=>{menuTimeline.pause('open',true).reversed(false);desiredOpen=true;phase='held open';gsap.set(panels,{xPercent:0,yPercent:0,rotation:0,autoAlpha:1});reflect();};studyCleanup.push(()=>menuTimeline.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.