관리 메뉴

웹솔루션개발 26년 노하우! 해피CGI의 모든것

[해피CGI][cgimall] GSAP 탭 콘텐츠 애니메이션(Tab content toggle) 본문

웹프로그램밍 자료실/기타 자료

[해피CGI][cgimall] GSAP 탭 콘텐츠 애니메이션(Tab content toggle)

해피CGI윤실장 2026. 8. 14. 09:07

탭을 클릭하면 선택한 콘텐츠가 부드럽게 전환되는 UI 예제입니다.

SplitText를 사용해 텍스트를 줄 단위로 나누고, GSAP 애니메이션으로 자연스럽게 등장시킵니다.
단순한 탭 메뉴보다 시각적인 효과가 좋아 랜딩페이지나 소개 섹션에 활용하기 좋습니다.
텍스트 중심의 콘텐츠 전환 효과를 구현할 때 참고하기 좋은 자료입니다.

HTML 구조

<div class="tabs-demo">

  <div class="tabs" id="tabs"></div>

  <div class="content-wrapper" id="contentWrapper"></div>

</div>



CSS 소스

* {

  box-sizing: border-box;

}

 

body {

  margin: 0;

  min-height: 100vh;

  display: grid;

  place-items: center;

  background: #f4f4f0;

  font-family: Inter, sans-serif;

  font-size: 16px;

}

 

.tabs-demo {

  width: 100%;

  max-width: 1020px;

  padding: 0 clamp(2rem, 8vw, 5rem);

}

 

.tabs {

  display: flex;

  justify-content: center;

  gap: 12px;

  margin-bottom: 32px;

  flex-wrap: wrap;

}

 

.tab {

  border: none;

  padding: 10px 16px;

  border-radius: 999px;

  font-size: 16px;

  font-weight: 500;

  letter-spacing: 0.02em;

  color: #000;

  cursor: pointer;

  background: rgba(0,0,0,0.08);

  transition: all 0.25s cubic-bezier(0.256, 0.009, 0.125, 0.997);

}

 

.tab&:hover {

  background: rgba(0,0,0,0.16);

}

 

.tab.active {

  background: #111;

  color: #fff;

}

 

.content-wrapper {

  position: relative;

  display: grid;

  grid-template-columns: 1fr;

}

 

.role-content {

  grid-column: 1;

  grid-row: 1;

  text-align: center;

  font-size: clamp(2rem, 3vw, 3rem);

  font-weight: 600;

  line-height: 1.15;

  letter-spacing: -0.03em;

  color: #000;

}

 

/* SplitText mask */

.himaaax-split-line {

  overflow: hidden;

  display: block;

}

 

JS 소스

 gsap.registerPlugin(SplitText);

 

const TAB_CONTENT = [

  {

    id: "teams",

    label: "Teams",

    text: "Work that moves at the speed of trust. Shared language, aligned direction, and a process that makes collaboration feel genuinely effortless."

  },

  {

    id: "founders",

    label: "Founders",

    text: "Turn early conviction into something people actually want. From rough idea to refined product — without losing what made it compelling."

  },

  {

    id: "designers",

    label: "Designers",

    text: "Craft interfaces that speak before a single word is read. Visual systems built to carry meaning across every touchpoint of a product."

  },

  {

    id: "engineers",

    label: "Engineers",

    text: "Build with precision on a foundation that scales. Architecture decisions that outlast the brief and hold up under real-world pressure."

  }

];

 

const DURATION = 0.4;

const STAGGER = 0.06;

const OVERLAP = "<0.24";

const Y_OFFSET = 150;

 

const tabsEl = document.getElementById("tabs");

const wrapper = document.getElementById("contentWrapper");

 

let activeIndex = 0;

let isAnimating = false;

let queuedIndex = null;

let tl = null;

 

TAB_CONTENT.forEach((item, i) => {

  const btn = document.createElement("button");

  btn.className = "tab" + (i === 0 ? " active" : "");

  btn.textContent = item.label;

  tabsEl.appendChild(btn);

 

  const panel = document.createElement("div");

  panel.className = "role-content";

  panel.textContent = item.text;

  wrapper.appendChild(panel);

});

 

const panels = [...document.querySelectorAll(".role-content")];

 

const splits = panels.map((el, i) => {

  return SplitText.create(el, {

    type: "lines",

    mask: "lines",

    linesClass: "himaaax-split-line",

    autoSplit: true,

    onSplit(self) {

      gsap.set(el, { opacity: 1 });

.......................................

첨부파일확인

.......................................

 

document.querySelectorAll(".tab").forEach((btn, i) => {

  btn.addEventListener("click", () => switchTab(i));

});

 

Comments