pin + timline + css

해적왕·2026년 1월 26일

고정된 상태로 스크롤할 때 안에서 스탭만 바뀌는 구조
(스크롤 내리면 고정된 상태로 다음 sea&U&I active)

<div class="history-container">
    <div class="history-visuals">
        <div class="history-visual-item active">
            <img src="." alt="">
        </div>
        <div class="history-visual-item">
            <img src="." alt="">
        </div>
        <div class="history-visual-item">
            <img src="." alt="">
        </div>
        <div class="history-visual-item">
            <img src="." alt="">
        </div>
        <div class="history-visual-item">
            <img src="." alt="">
        </div>
        <div class="history-visual-item">
            <img src="." alt="">
        </div>
    </div>
    <div class="history-contents">
        <div class="history-steps">
            <div class="step active">
                <div>vintage love</div>
            </div>
            <div class="step">
                <div>highway love</div>
            </div>
            <div class="step">
                <div>hate my home</div>
            </div>
            <div class="step">
                <div>Still On My Brain</div>
            </div>
            <div class="step">
                <div>miko!</div>
            </div>
            <div class="step">
                <div>Sea&U&I</div>
            </div>
        </div>
        <div class="history-details">
            <div class="step active">
                <div class="title">vintage<br /> love</div>
            </div>
            <div class="step">
                <div class="title">highway<br /> love</div>
            </div>
            <div class="step">
                <div class="title">hate my<br /> home</div>
            </div>
            <div class="step">
                <div class="title">Still On<br /> My Brain</div>
            </div>
            <div class="step">
                <div class="title">miko!</div>
            </div>
            <div class="step">
                <div class="title">Sea&U&I</div>
            </div>
        </div>
    </div>
</div>

js

class Section {
  constructor() {
    this.section = document.querySelector('.section');

    this.spheres = this.section.querySelectorAll('.history-visuals .history-visual-item');
    this.indicators = this.section.querySelectorAll('.history-steps .step');
    this.descriptions = this.section.querySelectorAll('.history-details .step');

    this.total = this.spheres.length;
    this.activeIndex = -1;

    this._pending = null; // 예약된 delayedCall 저장
    this.init();
  }

  endPoint(num) {
    return () => `+=${window.innerHeight * num}`;
  }

  clearAllActive() {
    this.spheres.forEach(el => el.classList.remove('active'));
    this.indicators.forEach(el => el.classList.remove('active'));
    this.descriptions.forEach(el => el.classList.remove('active'));
  }

  setActive(index) {
    index = Math.max(0, Math.min(this.total - 1, index));
    if (index === this.activeIndex && !this._pending) return;

    if (this._pending) this._pending.kill();

    this.clearAllActive();

    this._pending = gsap.delayedCall(0.25, () => {
      this.spheres[index]?.classList.add('active');
      this.indicators[index]?.classList.add('active');
      this.descriptions[index]?.classList.add('active');
      this.activeIndex = index;
      this._pending = null;
    });
  }

  init() {
    ScrollTrigger.create({
      trigger: this.section,
      start: 'top top',
      end: this.endPoint(3.5),
      pin: this.section,
      scrub: 1,
      invalidateOnRefresh: true,
      onUpdate: (self) => {
        const index = Math.round(self.progress * (this.total - 1));
        this.setActive(index);
      }
    });

    this.setActive(0);
  }
}
.history-container {
    .history-visuals {
        position: absolute;

        .history-visual-item {
            position: absolute;
            opacity: 0;
        }
    }

    .history-contents {
        .history-steps {
            .step {
                opacity: 0.3;
                z-index: 0;
                transition: opacity 0.3s cubic-bezier(0.45, 0, 0.55, 1);
            }
        }

        .history-details {
            position: relative;
            
            .step {
                transform: translateX(-30px);
                opacity: 0;
                position: absolute;
                right: 0;
                bottom: 0;
                z-index: 0;
                  transition: opacity .25s ease-in-out, transform .25s ease-out;

                &.active {
                    transform: translateX(0);
                }
            }
        }
    }

    .active{
        z-index: 1;
        opacity: 1;
        pointer-events: auto;
    }
}

0개의 댓글