ESE Agency

์ „ํ˜œ๋ฆฐยท2024๋…„ 4์›” 4์ผ
0

Portfolio

๋ชฉ๋ก ๋ณด๊ธฐ
8/11

๐Ÿ’ป ESE Agency ํด๋ก  ์ฝ”๋”ฉ

  • ์‚ฌ์ดํŠธ๋ช…: ESE Agency
  • ์ œ์ž‘๊ธฐ๊ฐ„: 24.03.06 ~ 24.03.11(5์ผ ์†Œ์š”)
  • ์‚ฌ์šฉ์–ธ์–ด: html, css, js
  • ๋ถ„๋ฅ˜: ๋ฐ˜์‘ํ˜•

๐Ÿ” Main Point

  • Canvas ์‚ฌ์šฉ ์—†์ด ์ด๋ฏธ์ง€ ์‹œํ€€์Šค ๊ตฌํ˜„ํ•˜๊ธฐ
  • Scroll Interaction
  • ๋ทฐํฌํŠธ ๋‹จ์œ„(vh, lvh, svh, dvh)
  • ๋“ฑ์ฐจ์ˆ˜์—ด์„ ์ด์šฉํ•˜์—ฌ n๋ฒˆ์งธ ์ž์‹์š”์†Œ ๊ตฌํ•˜๊ธฐ
  • ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ๋กœ PC์™€ Mobile ๊ตฌ๋ถ„ํ•˜๊ธฐ

Canvas ์‚ฌ์šฉ ์—†์ด ์ด๋ฏธ์ง€ ์‹œํ€€์Šค ๊ตฌํ˜„ํ•˜๊ธฐ

์‹ค์ œ ESE Agency ์‚ฌ์ดํŠธ์—์„œ๋Š” canvas๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ์‹œํ€€์Šค๋ฅผ ๊ตฌํ˜„ ํ•˜์˜€์ง€๋งŒ ๋‚˜๋Š” ๋น„๊ต์  ๊ฐ„๋‹จํ•œ img ํƒœ๊ทธ์™€ gsap์„ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…ํ–ˆ๋‹ค.

HTML

sticky์˜ ๋ถ€๋ชจ, sticky๊ฐ€ ๋  ์š”์†Œ, ๊ทธ๋ฆฌ๊ณ  ์‹œํ€€์Šค๊ฐ€ ๋  ์ด๋ฏธ์ง€๋“ค์„ sticky ์š”์†Œ ์•ˆ์— ๋„ฃ์–ด ๋งˆํฌ์—… ํ•˜์˜€๋‹ค.
๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ๊ตฌ๊ฐ„์— ์ง„์ž…ํ•˜๊ธฐ ์ „์—๋„ ์ด๋ฏธ์ง€๊ฐ€ ๋…ธ์ถœ๋  ์ˆ˜ ์žˆ๋„๋ก ๋งจ ๋งˆ์ง€๋ง‰ img ํƒœ๊ทธ์—๋Š” is-visible ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.

  <div class="container">
    <div class="sticky">
      <img src="./images/ese-hero-sequence00.webp" alt="" />
      <img src="./images/ese-hero-sequence01.webp" alt="" />
      <img src="./images/ese-hero-sequence02.webp" alt="" />
      <img src="./images/ese-hero-sequence03.webp" alt="" />
      <img src="./images/ese-hero-sequence04.webp" alt="" />
      <img src="./images/ese-hero-sequence05.webp" alt="" />
      
	  <!-- ...์ดํ•˜ ์ƒ๋žต(์ด๋ฏธ์ง€ ์ด ๊ฐœ์ˆ˜: 50) -->
      
      <img src="./images/ese-hero-sequence49.webp" class="is-visible" alt="" />
    </div>
  </div>

CSS

  • sticky์˜ ๋ถ€๋ชจ ์š”์†Œ์— ๋†’์ด ๊ฐ’์„ ์ฃผ์—ˆ๋‹ค.
  • sticky ์‚ฌ์šฉํ•  ์š”์†Œ์— ํ•ด๋‹น ์†์„ฑ ์ถ”๊ฐ€ ๋ฐ ์œ„์น˜ ๊ฐ’ top๊ณผ ๋†’์ด๊ฐ’ 100vh์„ ์ ์šฉ ํ•˜์˜€๋‹ค.
  • ์ด๋ฏธ์ง€๋Š” absolute๋กœ ๊ฒน์ณ ๋†“์€ ๋‹ค์Œ is-visible์ด๋ผ๋Š” ํด๋ž˜์Šค๊ฐ€ ๋ถ™์„ ๊ฒฝ์šฐ์—๋งŒ opacity: 1์„ ์ฃผ์–ด ํ™”๋ฉด์— ๋ณด์—ฌ์ง€๋„๋ก ํ•˜์˜€๋‹ค.
    .container {
      height: 200vh;
    }
    .sticky {
      position: sticky;
      top: 0;
      width: 100%;
      height: 100vh;
    }
    .sticky img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
      opacity: 0;
    }
    .sticky img.is-visible {
      opacity: 1;
    }

JS

  • ์šฐ์„  gsap์˜ onUpdate ๋ฉ”์„œ๋“œ์™€ progress๋กœ ์ด๋ฒคํŠธ ์ง„ํ–‰๋ฅ ์„ ๊ตฌํ•œ๋‹ค.
    ์ด๋ฒคํŠธ ์ง„ํ–‰๋ฅ (self.progress)์€ 0๊ณผ 1 ์‚ฌ์ด์˜ ๊ฐ’์ด๋‹ค. ์—ฌ๊ธฐ์„œ 0์€ ์‹œ์ž‘, 0.5๋Š” ์ค‘๊ฐ„ ์™„๋ฃŒ, 1์€ ์™„๋ฃŒ๋‹ค.

์ด๋ฏธ์ง€์˜ ์ด ๊ฐœ์ˆ˜์™€ ์ด๋ฒคํŠธ ์ง„ํ–‰๋ฅ ์„ ๊ณฑํ•˜๋ฉด ์Šคํฌ๋กค ํ•  ๋•Œ ๋งˆ๋‹ค 0 ~ 49 ์‚ฌ์ด์˜ ๊ฐ’์ด ์ถœ๋ ฅ๋œ๋‹ค. (์—ฌ๊ธฐ์—์„œ ๋งํ•˜๋Š” ์ด๋ฏธ์ง€ ์ด๊ฐœ์ˆ˜ = length - 1)
Math.round()๋กœ ๋ฐ˜์˜ฌ๋ฆผ์„ ํ•  ๊ฒฝ์šฐ ์†Œ์ˆ˜ -> ์ •์ˆ˜๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค.

console.log(Math.round((total * self.progress)))
๋‚˜์˜ ๊ฒฝ์šฐ ์ด๋ฏธ์ง€ ์ธ๋ฑ์Šค๊ฐ€ '๋‚ด๋ฆผ์ฐจ์ˆœ'์œผ๋กœ ๋…ธ์ถœ๋˜๊ธฐ๋ฅผ ํฌ๋งํ•˜์—ฌ ๋ฐ˜์˜ฌ๋ฆผ ํ•œ ๊ฐ’์„ ๋‹ค์‹œ ์ด๋ฏธ์ง€ ์ด ๊ฐœ์ˆ˜์—์„œ ๋บ๋‹ค.
Math.round((total * self.progress)) -> total - Math.round((total * self.progress))

๊ทธ๋Ÿผ ์•„๋ž˜์™€ ๊ฐ™์ด ์Šคํฌ๋กค ํ•  ๋•Œ๋งˆ๋‹ค 49 ~ 0 ์‚ฌ์ด์˜ ๊ฐ’์ด ์ถœ๋ ฅ๋œ๋‹ค.

console.log(total - Math.round((total * self.progress)))

์ด๋ ‡๊ฒŒ ๊ตฌํ•œ ๊ฐ’์„ index๋กœ ํ™œ์šฉํ•˜์—ฌ ์Šคํฌ๋กค ์‹œ index์— ํ•ด๋‹นํ•˜๋Š” ์ด๋ฏธ์ง€ ํƒœ๊ทธ์— is-visible ํด๋ž˜์Šค๋ฅผ ๋ถ™์—ฌ ํ™”๋ฉด์— ๋ณด์—ฌ์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ฉด ์™„์„ฑ์ด๋‹ค.

    ScrollTrigger.create({
      trigger: '.sticky',
      start: 'top top',
      end: 'bottom top',
      onUpdate: function(self) {
        const imageEls = document.querySelectorAll('.sticky img');
        const total = Array.from(imageEls).length - 1; // length๋Š” 1๋ถ€ํ„ฐ, index๋Š” 0๋ถ€ํ„ฐ ์‹œ์ž‘. ๋”ฐ๋ผ์„œ -1์„ ํ•˜์—ฌ ์ธ๋ฑ์Šค๋กœ ํ™œ์šฉํ•˜๊ธฐ
        const currentIndex = total - Math.round((total * self.progress)); // 50 ~ 0 ์‚ฌ์ด ์ˆซ์ž ์ถœ๋ ฅ(๋‚ด๋ฆผ์ฐจ์ˆœ)
        // const currentIndex = Math.round(total * self.progress); // 0 ~ 50 ์‚ฌ์ด ์ˆซ์ž ์ถœ๋ ฅ(์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ง„ํ–‰๋˜๊ธธ ๋ฐ”๋ผ๋ฉด ํ•ด๋‹น ์ฝ”๋“œ ์‚ฌ์šฉ)
        const currentImage =  imageEls[currentIndex];
        const CLASSNAME = 'is-visible';
        
        imageEls.forEach(function(imageEl, index) {
          if(imageEl.classList.contains(CLASSNAME)) {
            imageEl.classList.remove(CLASSNAME); // ๋ชจ๋“  ์ด๋ฏธ์ง€์— is-visible ํด๋ž˜์Šค ์ œ๊ฑฐ
          }

          currentImage.classList.add(CLASSNAME); // ํ˜„์žฌ ์Šคํฌ๋กค ํ•˜์—ฌ ๊ตฌํ•œ index์— ํ•ด๋‹นํ•˜๋Š” ์ด๋ฏธ์ง€์— is-visible ํด๋ž˜์Šค ์ถ”๊ฐ€
        });
      }
    })

๐Ÿ“‚ ์™„์„ฑ์ฝ”๋“œ



Scroll Interaction

ESE Agency ์‚ฌ์ดํŠธ์—์„œ ๊ฐ€์žฅ ๊ตฌํ˜„ํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ํšจ๊ณผ ์ค‘ ํ•˜๋‚˜์˜€๋Š”๋ฐ, ์Šคํฌ๋กค ํ•  ๋•Œ๋งˆ๋‹ค ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ”๊ฐ€ ์ฑ„์›Œ์ง€๋ฉฐ, ํ…์ŠคํŠธ๋„ ์Šคํฌ๋กค ์ง„ํ–‰๋ฅ ๋งŒํผ ์™ผ์ชฝ ๋ฐฉํ–ฅ์œผ๋กœ ์›€์ง์ธ๋‹ค. ์ด๋ฒคํŠธ๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ ๋งˆ๋‹ค ํ•œ์žฅ ์”ฉ ๋„˜์–ด๊ฐ€๋Š” ๋ถ€๋ถ„์€ swiper๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ž‘์—…ํ–ˆ๋‹ค.

HTML

sticky๋ฅผ ์‚ฌ์šฉํ•  ์š”์†Œ ์•ˆ์— swiper ๊ตฌ์กฐ์™€ progress ๊ตฌ์กฐ๋ฅผ ๋„ฃ๊ณ , sticky ๋ถ€๋ชจ๋ฅผ ๋งŒ๋“ค์–ด ํ•œ๋ฒˆ ๊ฐ์‹ผ๋‹ค. ์—ฌ๊ธฐ์„œ swiper์™€ progress๋Š” ํ˜•์ œ ๊ตฌ์กฐ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค.

<div class="sticky-container">
  <div class="sticky">
    <div class="swiper">
      <div class="swiper-wrapper">
        <div class="swiper-slide slide1">
          <div class="content" data-swiper-parallax-y="80%">
            slide1
          </div>
        </div>
		<!-- ...์ดํ•˜ ์ƒ๋žต(์Šฌ๋ผ์ด๋“œ ์ด ๊ฐœ์ˆ˜: 5) -->
        </div>
      </div>
    </div>
    <div class="progress-area">
      <div class="progress">
        <div class="progress-column">
          <span class="progress-fill"></span>
        </div>
        <!-- ...์ดํ•˜ ์ƒ๋žต(ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ” ์ด ๊ฐœ์ˆ˜: 5) -->
      </div>
    </div>
  </div>
</div>

CSS

  • sticky ๋ถ€๋ชจ์— ๋†’์ด ๊ฐ’ ์ง€์ •
  • sticky ๋  ์š”์†Œ์— sticky ์†์„ฑ๊ณผ ์œ„์น˜ ๊ฐ’, ๋†’์ด ๊ฐ’ ์ง€์ •
  • swiper์— height: 100%์„ ์ฃผ์–ด ๋†’์ด๊ฐ€ ๋ถ€๋ชจ ํฌ๊ธฐ ๋งŒํผ ๋˜๋„๋ก ํ•œ๋‹ค.
  • swiper์˜ parallax ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ swiper-slide์— overflow: hidden ์†์„ฑ์„ ์ค€๋‹ค.
  • progress ๊ตฌ์กฐ๋Š” absolute๋กœ ๋„์›Œ์„œ ์Šฌ๋ผ์ด๋“œ ์•„๋ž˜์— ํ•ญ์ƒ ๋”ฐ๋ผ ๋‹ค๋‹ˆ๋„๋ก ํ•œ๋‹ค. (sticky๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ฐฐ์น˜)
  • ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋กœ ๊ฒŒ์ด์ง€๊ฐ€ ์ฑ„์›Œ์งˆ progress-fill์—๋Š” width: 0 ๊ฐ’์„ ๊ฐ•์ œ๋กœ ์ฃผ์–ด block ์†์„ฑ ํŠน์ง•์ธ ๊ฐ€๋กœ ๋„ˆ๋น„๊ฐ€ ๋ถ€๋ชจ ํฌ๊ธฐ ๋งŒํผ ์ฐจ์ง€ํ•˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค.
.sticky-container {
  height: 500vh; /* ๋ถ€๋ชจ ๋†’์ด ๋งŒํผ sticky๋กœ ๊ณ ์ • */
}
.sticky {
  position: sticky;
  top: 0;
  width: 100%;
  height: 100vh;
}
.swiper {
  height: 100%; /* ํ•„์ˆ˜ ์†์„ฑ. ๋†’์ด ๊ฐ’์ด 100%๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ๋ฌดํ•œํ•ด์ง */
}
.swiper-slide {
  overflow: hidden;  
  /* overflow hidden์„ ์ฃผ์ง€ ์•Š์œผ๋ฉด ๋‹ค์Œ ์Šฌ๋ผ์ด๋“œ๊ฐ€ ๋ณด์ž„
  (next slide๋Š” transform -80%์œผ๋กœ ํ™œ์„ฑ ์Šฌ๋ผ์ด๋“œ์— ์ด๋ฏธ ์˜ฌ๋ผ์™€ ์žˆ์Œ!) */
}
.content {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  font-size: 5vw;
  font-weight: 700;
  color: #fff;
}
.progress-area {
  position: absolute; /* absolute๋กœ ์Šฌ๋ผ์ด๋“œ ์•„๋ž˜์— ํ•ญ์ƒ ๋”ฐ๋ผ๋‹ค๋‹˜ */
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 60px 60px;
  pointer-events: none;
}
.progress {
  display: flex;
  align-items: center;
  justify-content: center;
  column-gap: 20px;
  width: 100%;
  max-width: 1880px;
  margin: 0 auto;
}
.progress-column {
  flex: 1;
  height: 2px;
  overflow: hidden;
  background-color: rgba(255, 255, 255, 0.2);
  border-radius: 10px;
}
.progress-fill {
  display: block;
  width: 0; /* ์šฐ์„  ์ดˆ๊ธฐ 0 ๊ฐ’์œผ๋กœ ์„ธํŒ…. ์ถ”ํ›„ gsap์œผ๋กœ ๊ฒŒ์ด์ง€ ์ฑ„์šธ ์˜ˆ์ • */
  height: 100%;
  background-color: rgba(255, 255, 255, 0.96);
}

JS

const swiper = new Swiper('.swiper', {
  direction: 'vertical',
  parallax: true,
  speed: 1000,
  touchRatio: 0 // ๋“œ๋ž˜๊ทธ ๊ธˆ์ง€
});

const progressTl = gsap.timeline({
  scrollTrigger: {
    trigger: '.sticky-container',
    start: '0% 0%',
    end: '100% 100%',
    scrub: 0,
    // markers: true
  }
})

const progressEls = document.querySelectorAll('.progress-column');

progressEls.forEach(function(progressEl, index) {
  const progressFill = progressEl.querySelector('.progress-fill');

  progressTl.to(progressFill, {
    width: '100%',
    ease: 'none',
    onComplete: function() {
      swiper.slideTo(index + 1);
    },
    onReverseComplete: function() {
      swiper.slideTo(index - 1);
    }
  })
});

1. Swiper

const swiper = new Swiper('.swiper', {
  direction: 'vertical',
  parallax: true,
  speed: 1000,
  touchRatio: 0 // ๋“œ๋ž˜๊ทธ ๊ธˆ์ง€
});

์‚ฌ์šฉํ•œ ์˜ต์…˜

  • direction: 'vertical'
    ์„ธ๋กœํ˜• ์Šฌ๋ผ์ด๋“œ ์‚ฌ์šฉ์„ ์œ„ํ•ด vertical ๊ฐ’์„ ์ง€์ •ํ•˜์˜€๋‹ค. (๊ธฐ๋ณธ๊ฐ’: 'horizontal')

  • touchRatio: 0
    ๋“œ๋ž˜๊ทธ ๋น„์œจ์„ ์„ค์ •ํ•˜๋Š” ์†์„ฑ์œผ๋กœ, ๊ธฐ๋ณธ ๊ฐ’์€ 1์ด๋‹ค. ๋‚˜๋Š” ์Šคํฌ๋กค ์ด๋ฒคํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ์Šฌ๋ผ์ด๋“œ๋ฅผ ์ œ์–ดํ•  ๊ฒƒ์ด๋ฏ€๋กœ 0 ๊ฐ’์„ ์ฃผ์–ด ๋“œ๋ž˜๊ทธ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜์˜€๋‹ค.

  • parallax: true
    ๋‹จ์–ด ๊ทธ๋Œ€๋กœ parallax ํšจ๊ณผ๋ฅผ ์ค„ ์ˆ˜ ์žˆ๋Š” ์†์„ฑ์ด๋‹ค. html์— parallax ๊ด€๋ จ data ์†์„ฑ ์ถ”๊ฐ€๋„ ํ•ด์•ผํ•˜๋Š”๋ฐ ํšจ๊ณผ๊ฐ€ ์ง€์› ๋˜๋Š” ์š”์†Œ์™€ ์†์„ฑ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

parallax

1) ์ง€์› ์š”์†Œ

  • swiper ์ž์‹ ์š”์†Œ
  • ์Šฌ๋ผ์ด๋“œ ์ž์† ์š”์†Œ
  • Direct child elements of swiper. Parallax effect for such elements will depend on total slider progress.
  • Slides child elements. Parallax effect for such elements will depend on slide progress - Swiper API

๐Ÿ˜ฎ ์ฃผ์˜ํ•  ์ 
์Šฌ๋ผ์ด๋“œ(swiper-slide)์— ์ง์ ‘ parallax data ์†์„ฑ์„ ์ฃผ๊ฒŒ ๋˜๋ฉด ๋™์ž‘ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ ํ•˜์œ„ ์š”์†Œ์— ์ฃผ์–ด์•ผ ํ•œ๋‹ค!

2) ์ง€์› ์†์„ฑ

  • data-swiper-parallax : ์‹œ์ฐจ ์ „ํ™˜ ํ™œ์„ฑํ™”
    • number: ์ง„ํ–‰ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์š”์†Œ๋ฅผ ์ด๋™ํ•˜๊ธฐ ์œ„ํ•œ px ๋‹จ์œ„ ๊ฐ’์œผ๋กœ ์Šฌ๋ผ์ด๋“œ ์œ„์น˜(๋‹ค์Œ ๋˜๋Š” ์ด์ „)์— ๋”ฐ๋ผ ยฑ ๊ฐ’(px)์œผ๋กœ ์ด๋™ํ•œ๋‹ค.
    • percentage: ์ง„ํ–‰ ์ƒํ™ฉ๊ณผ ํฌ๊ธฐ์— ๋”ฐ๋ผ ์š”์†Œ๋ฅผ ์ด๋™ํ•˜๋ฉฐ ์Šฌ๋ผ์ด๋“œ ์œ„์น˜(๋‹ค์Œ ๋˜๋Š” ์ด์ „)์— ๋”ฐ๋ผ ํ•ด๋‹น ํฌ๊ธฐ(๊ฐ€๋กœ ๋„ˆ๋น„, ์„ธ๋กœ ๋†’์ด)์˜ ยฑ ๋น„์œจ๋งŒํผ ์ด๋™ํ•œ๋‹ค. ๋งŒ์•ฝ, ์š”์†Œ์˜ ๋„ˆ๋น„๊ฐ€ 400px์ด๊ณ  data-swiper-parallax="50%"๋ฅผ ์ง€์ •ํ•˜๋ฉด ยฑ 200px๋งŒํผ ์ด๋™ํ•œ๋‹ค.
  • data-swiper-parallax-x : x์ถ• ๋ฐฉํ–ฅ
  • data-swiper-parallax-y : y์ถ• ๋ฐฉํ–ฅ
  • data-swiper-parallax-scale : "๋น„ํ™œ์„ฑ"(ํ™œ์„ฑ ์Šฌ๋ผ์ด๋“œ๊ฐ€ ์•„๋‹˜) ์ƒํƒœ์ผ ๋•Œ ์‹œ์ฐจ ์š”์†Œ์˜ ํฌ๊ธฐ ๋น„์œจ
  • data-swiper-parallax-opacity : "๋น„ํ™œ์„ฑ"(ํ™œ์„ฑ ์Šฌ๋ผ์ด๋“œ๊ฐ€ ์•„๋‹˜) ์ƒํƒœ์ผ ๋•Œ ์‹œ์ฐจ ์š”์†Œ์˜ ๋ถˆํˆฌ๋ช…๋„
  • data-swiper-parallax-duration : ์‹œ์ฐจ ์š”์†Œ์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ง€์ • ์ „ํ™˜ ๊ธฐ๊ฐ„

๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋กœ ์ ์šฉ์ด ์ž˜ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.


2. GSAP

ํ˜„์žฌ swiper์—์„œ touchRatio 0์„ ์ฃผ์–ด ๋“œ๋ž˜๊ทธ ๊ธฐ๋Šฅ์„ ๋ง‰์€ ์ƒํƒœ์ด๋‹ค.
์Šคํฌ๋กค ์ด๋ฒคํŠธ๋กœ ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ” ๊ฒŒ์ด์ง€๋ฅผ ์ฑ„์šฐ๊ณ , ์ฑ„์›Œ์ง€๋ฉด ์ด์ „ ํ˜น์€ ๋‹ค์Œ ์Šฌ๋ผ์ด๋“œ๋กœ ๋„˜์–ด๊ฐ€๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด GSAP์„ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

const progressTl = gsap.timeline({
  scrollTrigger: {
    trigger: '.sticky-container',
    start: '0% 0%',
    end: '100% 100%',
    scrub: 0,
    // markers: true
  }
})

const progressEls = document.querySelectorAll('.progress-column');

progressEls.forEach(function(progressEl, index) {
  const progressFill = progressEl.querySelector('.progress-fill');

  progressTl.to(progressFill, {
    width: '100%',
    ease: 'none',
    onComplete: function() {
      swiper.slideTo(index + 1);
    },
    onReverseComplete: function() {
      swiper.slideTo(index - 1);
    }
  })
});
  1. ์šฐ์„  scrollTrigger ๊ธฐ์ค€์€ sticky-container์ด๋ฉฐ, ์‹œ์ž‘์ ๊ณผ ๋์ ์€ ๊ฐ๊ฐ 0% 0%, 100% 100%๋ฅผ ์ฃผ์–ด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์Šคํฌ๋กค ์ด๋ฒคํŠธ ์˜์—ญ์ด ๋  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
  2. ๊ทธ๋ฆฌ๊ณ  ๊ฒŒ์ด์ง€๋ฅผ ์ฑ„์šธ ์š”์†Œ์— ๋ฐ˜๋ณต๋ฌธ forEach๋ฅผ ๋Œ๋ฆฐ ํ›„ gsap.timeline์— ์—ฐ๊ฒฐํ•œ๋‹ค. ๊ทธ๋Ÿผ ์ „์ฒด ์ด๋ฒคํŠธ๋ฅผ 5๋“ฑ๋ถ„ํ•˜์—ฌ 1/5์ด ์ง€๋‚  ๋•Œ๋งˆ๋‹ค width: 100%๊ฐ€ ์ฑ„์›Œ์ง„๋‹ค.
  3. ํ•˜๋‚˜์˜ ํ”„๋กœ๊ทธ๋ž˜์Šค๋ฐ” ๊ฒŒ์ด์ง€๊ฐ€ 100% ์ฑ„์›Œ์ง€๋ฉด ์Šฌ๋ผ์ด๋“œ๋„ ๋„˜์–ด๊ฐ€์•ผํ•œ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด onComplete์™€ onReverseComplete ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค,

onComplete

  • ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์™„๋ฃŒ๋˜๋ฉด ํ˜ธ์ถœ๋œ๋‹ค.

console.log๋กœ onComplete ํ˜ธ์ถœ ์‹œ์ ์„ ํ™•์ธํ•ด๋ณด๋ฉด, ํ•˜๋‚˜์˜ ๊ฒŒ์ด์ง€ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ข…๋ฃŒ๋˜๋Š” ์‹œ์ ์ด๋‹ค.

progressTl.to(progressFill, {
  width: '100%',
  ease: 'none',
  onComplete: function() {
    console.log('complete');
  }
})

์ด๋•Œ ๋‹ค์Œ ์Šฌ๋ผ์ด๋“œ๋กœ ๋„˜์–ด๊ฐ€๋„๋ก ํ•˜๋ฉด ๋˜๋Š”๋ฐ swiper.slideTo() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šฌ๋ผ์ด๋“œ ์ „ํ™˜์„ ์‹คํ–‰ํ•œ๋‹ค.

swiper.slideTo( index , speed , runCallbacks )

slideTo ๋ฉ”์„œ๋“œ๋Š” ์Šฌ๋ผ์ด๋“œ๋ฅผ ํŠน์ • ์ธ๋ฑ์Šค๋กœ ์ด๋™์‹œํ‚ค๋Š” ๋ฉ”์„œ๋“œ
- ์ธ๋ฑ์Šค๋Š” ์Šฌ๋ผ์ด๋“œ์˜ ์ˆœ์„œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋ฉฐ, 0๋ถ€ํ„ฐ ์‹œ์ž‘

  • index: ์Šฌ๋ผ์ด๋“œ์˜ ์ธ๋ฑ์Šค ๋ฒˆํ˜ธ
  • speed: ์ „ํ™˜ ๊ธฐ๊ฐ„(ms)
  • runCallbacks: boolean ํƒ€์ž…, false๋กœ ์„ค์ •ํ•˜๋ฉด ์ „ํ™˜ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์Œ(๊ธฐ๋ณธ๊ฐ’ true)

onReverseComplete

  • ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์—ญ๋ฐฉํ–ฅ์—์„œ ๋‹ค์‹œ ์‹œ์ž‘๋  ๋•Œ ํ˜ธ์ถœ ๋œ๋‹ค.
    ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด 2๋ฒˆ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚˜๊ณ  3๋ฒˆ์—์„œ ๋‹ค์‹œ 2๋ฒˆ์œผ๋กœ ์ง„์ž…ํ•˜์ž๋งˆ์ž reverse complete๊ฐ€ ํ˜ธ์ถœ๋˜์—ˆ๋‹ค.
    ์ด๋•Œ๋Š” ์ด์ „ ์Šฌ๋ผ์ด๋“œ๋กœ ์ด๋™ํ•˜๋„๋ก slideTo(index - 1) ๊ฐ’์„ ๋„ฃ์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

๐Ÿ“‚ ์™„์„ฑ์ฝ”๋“œ



๋ทฐํฌํŠธ ๋‹จ์œ„(vh, lvh, svh, dvh)

ESE Agency ์‚ฌ์ดํŠธ๋Š” svh ๋‹จ์œ„๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํƒ€์ผ์„ ์ค€ ๋ถ€๋ถ„๋“ค์ด ๋งŽ์•˜๋Š”๋ฐ, ๊ทธ๋ž˜์„œ ์ด๋ฒˆ์— CSS ๋ทฐํฌํŠธ ๋‹จ์œ„์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

vh ๋‹จ์œ„๋Š” ์œ ์ €์—๊ฒŒ ๋ณด์ด๋Š” ์˜์—ญ์˜ ํฌ๊ธฐ๊ฐ’์„ ์ฐธ์กฐํ•˜๊ธฐ ์œ„ํ•ด 2015๋…„์— ์ถ”๊ฐ€๋˜์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ € ์Šคํฌ๋กค ์‹œ ์ฃผ์†Œ์ฐฝ์˜ ํฌ๊ธฐ ๋ณ€ํ™”์— ๋”ฐ๋ฅธ ๋ทฐํฌํŠธ ์˜์—ญ์˜ ํฌ๊ธฐ ๋ณ€ํ™”์™€ ๊ด€๋ จ๋œ ๋ชจํ˜ธํ•จ ๋•Œ๋ฌธ์—, 2021๋…„ dvh, lvh, svh ๋‹จ์œ„๊ฐ€ ์ƒˆ๋กœ ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.

vh

๋งŒ์•ฝ % ๋‹จ์œ„๋กœ ์š”์†Œ ๋†’์ด๋ฅผ ํ™”๋ฉด(๋ทฐํฌํŠธ)์„ ๊ฝ‰ ์ฑ„์šฐ๋„๋ก ์„ค์ •ํ•˜๋ ค๋ฉด, ๋ถ€๋ชจ ์š”์†Œ๋“ค์˜ ๋†’์ด๋ฅผ ๋ชจ๋‘ 100%๋กœ ์„ค์ •ํ•ด์ค˜์•ผ ํ•œ๋‹ค. ํ•˜์ง€๋งŒ vh ๋‹จ์œ„๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€๋ชจ ์š”์†Œ์˜ ๋†’์ด์™€ ๊ด€๊ณ„ ์—†์ด, ๋ทฐํฌํŠธ์˜ ๋†’์ด๋กœ ๋ฐ”๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

vh๋Š” ์ฃผ์†Œ์ฐฝ์ด ์ž‘์•„์กŒ์„ ๋•Œ์˜ ๋ทฐํฌํŠธ ๋†’์ด๊ฐ€ ๋ฐ˜์˜๋œ๋‹ค. ๊ทธ๋ž˜์„œ ํŠน์ • ํŽ˜์ด์ง€๋ฅผ ์ฒ˜์Œ ์ ‘์†ํ•ด์„œ ๋ธŒ๋ผ์šฐ์„œ์˜ ์ฃผ์†Œ์ฐฝ์ด ์ปค์ง„ ์ƒํƒœ์—์„œ๋Š” 100vh๋กœ ์„ค์ •ํ•œ ์š”์†Œ๊ฐ€ ์ž˜๋ ค์ ธ์„œ ๋ณด์ธ๋‹ค.

vh๋Š” ๋ธŒ๋ผ์šฐ์ € ์ž์ฒด UI์˜ ํฌ๊ธฐ ๋ณ€ํ™”๋ฅผ ๋ฐ˜์˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ด๋ฏธ์ง€ ์ถœ์ฒ˜: GoogleIO 2022

lvh(large viewport height)

lvh๋Š” vh์™€ ๋™์ผํ•˜๊ฒŒ ์ฃผ์†Œ์ฐฝ์˜ ํฌ๊ธฐ๊ฐ€ ์ถ•์†Œ๋˜์–ด ์œ ์ €์—๊ฒŒ ๋ณด์ด๋Š” ์˜์—ญ์ด ์ œ์ผ ํด ๋•Œ๋ฅผ ๋ฐ˜์˜ํ•œ๋‹ค.

svh(small viewport height)

svh๋Š” ์ฃผ์†Œ์ฐฝ์˜ ํฌ๊ธฐ๊ฐ€ ์ปค์ ธ์„œ ์œ ์ €์—๊ฒŒ ๋ณด์ด๋Š” ์˜์—ญ์ด ์ œ์ผ ์ž‘์„ ๋•Œ์˜ ๋†’์ด๋ฅผ ๋ฐ˜์˜ํ•œ๋‹ค.

dvh(dynamic viewport height)

dvh๋Š” ๋ธŒ๋ผ์šฐ์ € ์ž์ฒด UI์˜ ํฌ๊ธฐ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๊ฐฑ์‹ ๋˜์–ด, ์‹ค์ œ ์œ ์ €๊ฐ€ ๋ณด๋Š” ์˜์—ญ์˜ ํฌ๊ธฐ๋ฅผ ํ•ญ์ƒ ๋ฐ˜์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.



๋“ฑ์ฐจ์ˆ˜์—ด์„ ์ด์šฉํ•˜์—ฌ n๋ฒˆ์งธ ์ž์‹์š”์†Œ ๊ตฌํ•˜๊ธฐ

๊ฐ ์š”์†Œ์— transition-delay๋ฅผ ์ฃผ์–ด ํšจ๊ณผ๋ฅผ ์ฃผ์–ด์•ผ ํ–ˆ๋Š”๋ฐ ๋ฆฌ์‚ฌ์ด์ง• ๋  ๋•Œ๋งˆ๋‹ค ๋ ˆ์ด์•„์›ƒ ๋ฐฐ์น˜๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด delay ๊ฐ’์„ ๋™์ ์œผ๋กœ ๊ณ„์† ๋ณ€๊ฒฝํ•ด์•ผ๋งŒ ํ–ˆ๋‹ค. ๋“ฑ์ฐจ์ˆ˜์—ด์„ ์ด์šฉํ•ด์„œ ํ•ด๊ฒฐ ํ–ˆ๋Š”๋ฐ ๊ณต์‹์„ ๋˜ ๊นŒ๋จน์„ ๊ฒƒ์„ ๋Œ€๋น„ํ•ด์„œ ์ •๋ฆฌํ•ด๋ณด๊ณ ์ž ํ•œ๋‹ค.

๊ณต์‹

a + b(n - 1) = a + (bn - b)

  • a: ์ฒซ ๋ฒˆ์งธ ์ˆซ์ž
  • b: ๊ฐ ๊ฐ’์˜ ์ฐจ์ด

[3์—ด]
1 / 2 / 3
4 / 5 / 6
7 / 8 / 9
...

1, 4, 7 ... (3์—ด์˜ 1๋ฒˆ ์งธ ์—ด)
-> 1 + 3(n - 1) = 3n - 2

2, 5, 8 ... (3์—ด์˜ 2๋ฒˆ ์งธ ์—ด)
-> 2 + 3(n - 1) = 3n - 1

3, 6, 9 ... (3์—ด์˜ 3๋ฒˆ ์งธ ์—ด)
-> 3 +3(n - 1) = 3n

[4์—ด]
1 / 2 / 3 / 4
5 / 6 / 7 / 8
9 / 10 / 11 / 12
...

1, 5, 9 ... (4์—ด์˜ 1๋ฒˆ ์งธ ์—ด)
-> 1 + 4(n - 1) = 4n - 3

2, 6, 10 ... (4์—ด์˜ 2๋ฒˆ ์งธ ์—ด)
-> 2 + 4(n - 1) = 4n - 2

3, 7, 11 ... (4์—ด์˜ 3๋ฒˆ ์งธ ์—ด)
-> 3 + 4(n - 1) = 4n - 1

4, 8, 12 ... (4์—ด์˜ 4๋ฒˆ ์งธ ์—ด)
-> 4 + 4(n - 1) = 4n



๋ฏธ๋””์–ด ์ฟผ๋ฆฌ๋กœ PC์™€ Mobile ๊ตฌ๋ถ„ํ•˜๊ธฐ

ESE Agency ์‚ฌ์ดํŠธ๋Š” hover ํšจ๊ณผ๋ฅผ @media(hover: hover) ์•ˆ์— ๋ชจ๋‘ ์„ ์–ธํ–ˆ๋‹ค. ๋ฏธ๋””์–ด์ฟผ๋ฆฌ hover์™€ pointer๋Š” ์ด๋ฒˆ์— ์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ๋œ ๋ถ€๋ถ„์ด๋ผ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค.

์šฐ์„  hover๋Š” ํŠน์ • ์š”์†Œ์— ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ ํ–‰๋™์„ ์ทจํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

a:hover {
  color: #fff;
  background-color: #000;
}

ํ„ฐ์น˜์Šคํฌ๋ฆฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋ฐ”์ผ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ hover ๋™์ž‘์ด ์—†์ง€๋งŒ ๋ชจ๋ฐ”์ผ์—์„œ๋„ ์•„์ฃผ ์ž ๊น๋™์•ˆ ํšจ๊ณผ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. ํ„ฐ์น˜์Šคํฌ๋ฆฐ์—์„œ ๊พน ๋ˆ„๋ฅด๋Š” ๋™์ž‘ ๋“ฑ๋„ hover๋กœ ์ธ์‹ํ•˜๊ธฐ์— ๋งˆ์šฐ์Šค ๋“ฑ์˜ ์žฅ์น˜๊ฐ€ ์—†์„ ๋• ์˜๋„์น˜ ์•Š์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์„ ์‚ฌํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

์ด๊ฒƒ์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ๋ฐ”๋กœ ๋ฏธ๋””์–ด์ฟผ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. hover์™€ pointer ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•˜๋ฉด ๋ชจ๋ฐ”์ผ๊ณผ ๋ฐ์Šคํฌํ†ฑ์„ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์ด ์–ด๋Š ์ •๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

hover

hover ๋ฏธ๋””์–ด ๊ธฐ๋Šฅ์€ ๊ธฐ๋ณธ ํฌ์ธํŒ… ์žฅ์น˜๋กœ ํŽ˜์ด์ง€์˜ ์š”์†Œ๋ฅผ ํ˜ธ๋ฒ„๋ง ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ๊ธฐ์กด CSS์˜ ๊ฐ€์ƒ ์„ ํƒ์ž :hover๋Š” ํ˜ธ๋ฒ„๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์˜ ์Šคํƒ€์ผ์„ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜์ง€๋งŒ, ๋ฏธ๋””์–ด์ฟผ๋ฆฌ์˜ hover๋Š” ์žฅ์น˜์˜ ์ž…๋ ฅ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ˜ธ๋ฒ„๊ฐ€ ๋ฐœ์ƒ ๊ฐ€๋Šฅํ•œ์ง€ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.

hover ๋ฏธ๋””์–ด๋Š” ๋‹ค์Œ์˜ ๋‘ ๊ฐ€์ง€ ๊ฐ’์„ ์ง€์›ํ•œ๋‹ค.

  • none
    1) ์ฃผ ํฌ์ธํŒ… ์žฅ์น˜๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ
    2) ์ฃผ ํฌ์ธํŒ… ์žฅ์น˜๊ฐ€ hover๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
    3) hover๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์ธ ๋ฐฉ๋ฒ•์ด ์•„๋‹Œ ๋ถˆํŽธํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ hover ํ•˜๋Š” ๊ฒฝ์šฐ (ex: ํ„ฐ์น˜์Šคํฌ๋ฆฐ์˜ long tab์€ hover๋กœ ๊ฐ„์ฃผ๋  ์ˆ˜ ์žˆ์œผ๋‚˜ ์ผ๋ฐ˜์ ์ธ hover ๋™์ž‘์€ ์•„๋‹˜)

  • hover
    ๊ธฐ๋ณธ ํฌ์ธํŒ… ์žฅ์น˜๊ฐ€ ํŠน์ • ์—˜๋ฆฌ๋จผํŠธ ์œ„๋กœ ์‰ฝ๊ฒŒ hover ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ
    ๋Œ€ํ‘œ์ ์œผ๋กœ ๋งˆ์šฐ์Šค์™€ ๊ฐ™์€ ์žฅ์น˜๋ฅผ ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

** ํ„ฐ์น˜์Šคํฌ๋ฆฐ์ด ์ฃผ ํฌ์ธํŒ… ์žฅ์น˜์ด์ง€๋งŒ ๋งˆ์šฐ์Šค๋ฅผ ๋™์‹œ์— ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ
๋น„๋ก ๋งˆ์šฐ์Šค๋ฅผ ํ†ตํ•ด์„œ hover ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ฃผ ํฌ์ธํŒ…์€ ํ„ฐ์น˜์Šคํฌ๋ฆฐ์ด๋ฏ€๋กœ hover : none์ด ๋œ๋‹ค.

Pointer

Pointer(ํฌ์ธํ„ฐ) ๋ฏธ๋””์–ด ๊ธฐ๋Šฅ์€ ๋งˆ์šฐ์Šค์™€ ๊ฐ™์€ ํฌ์ธํŒ… ์žฅ์น˜์˜ ์กด์žฌ ์—ฌ๋ถ€์™€ ์ •ํ™•์„ฑ์„ ํŒ๋ณ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. ํฌ์ธํŒ… ์žฅ์น˜๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ๋Š” ๊ฒฝ์šฐ ํฌ์ธํ„ฐ ๋ฏธ๋””์–ด ๊ธฐ๋Šฅ์€ ์‚ฌ์šฉ์ž ์—์ด์ „ํŠธ์—์„œ ๊ฒฐ์ •ํ•œ "๊ธฐ๋ณธ" ํฌ์ธํŒ… ์žฅ์น˜์˜ ํŠน์„ฑ์„ ๋ฐ˜์˜ํ•ด์•ผ ํ•œ๋‹ค.

Pointer ๋ฏธ๋””์–ด๋Š” ๋‹ค์Œ์˜ 3๊ฐ€์ง€ ๊ฐ’์„ ์ง€์›ํ•œ๋‹ค.

  • none
    ํฌ์ธํŒ… ์žฅ์น˜๊ฐ€ ์—†๋‹ค.

  • coarse
    ๊ธฐ๋ณธ ์žฅ์น˜๊ฐ€ ํ„ฐ์น˜์Šคํฌ๋ฆฐ์ด๋‚˜ ํ‚ค๋„ฅํŠธ์ฒ˜๋Ÿผ ์ •ํ™•๋„๋Š” ๋†’์ง€ ์•Š์ง€๋งŒ ํฌ์ธํŒ…์€ ๊ฐ€๋Šฅํ•˜๋‹ค.

  • fine
    ๊ธฐ๋ณธ ์žฅ์น˜๊ฐ€ ๋งˆ์šฐ์Šค๋‚˜ ํ„ฐ์น˜ ํŒจ๋“œ์ฒ˜๋Ÿผ ์ •ํ™•ํ•œ ํฌ์ธํŒ…์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

@media (hover: hover) and (pointer: fine) {
  button:hover {
    font-weight: 700;
    color: red;
  }
}

์œ„ ์ฝ”๋“œ๋Š” hover๋ฅผ ์ง€์›ํ•˜๋ฉด์„œ, pointer๋Š” ๋งˆ์šฐ์Šค์™€ ๊ฐ™์€ ์ •ํ™•ํ•œ ํฌ์ธํ„ฐ ์žฅ์น˜๋ฅผ ์ง€์›ํ•˜๋Š” ํ™˜๊ฒฝ์—์„œ๋งŒ hover๊ฐ€ ๋™์ž‘ ๋˜๋„๋ก ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

์•„๋ž˜ ์˜ˆ์‹œ๋ฅผ PC์™€ ๋ชจ๋ฐ”์ผ ๋””๋ฐ”์ด์Šค์—์„œ ๊ฐ๊ฐ ํ™•์ธํ•ด๋ณด์ž.



์ฐธ๊ณ  ์‚ฌ์ดํŠธ
https://gsap.com/docs/v3/GSAP/Timeline/progress()/
https://swiperjs.com/swiper-api
https://jisiq.com/css/css-vh-dvh-lvh-svh
https://yozm.blog/css-new-viewport-svh-etc
https://blog.teamelysium.kr/css-viewport-units
https://homzzang.com/b/css-295
https://seons-dev.tistory.com/entry/css-hover-Media-query
https://paperblock.tistory.com/164

profile
์ฝ”๋”ฉ์ชผ์•„

0๊ฐœ์˜ ๋Œ“๊ธ€