Bennett & Clive

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

Portfolio

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

๐Ÿ’ป Bennett & Clive ํด๋ก  ์ฝ”๋”ฉ

  • ์‚ฌ์ดํŠธ๋ช…: Bennett & Clive
  • ์ œ์ž‘๊ธฐ๊ฐ„: 24.03.28 ~ 24.03.31(4์ผ ์†Œ์š”)
  • ์‚ฌ์šฉ์–ธ์–ด: html, css, js
  • ๋ถ„๋ฅ˜: ๋ฐ˜์‘ํ˜•

๐Ÿ” Main Point

  • ์Šคํฌ๋กค ์‹œ ๊ณ ์ •๋œ ์ƒํƒœ์—์„œ ๋™์ผํ•œ ํ…์ŠคํŠธ๊ฐ€ ํ•œ์ชฝ์—์„œ๋Š” ๋ณด์—ฌ์ง€๊ณ  ๋‹ค๋ฅธ ์ชฝ์—์„œ๋Š” ์‚ฌ๋ผ์ง€๋Š” ํšจ๊ณผ ๊ตฌํ˜„
  • ์ง€์—ญ๋ณ„ ์‹œ๊ฐ„ ๊ตฌํ•˜๊ธฐ
  • CSS scrollbar-gutter ์†์„ฑ

์Šคํฌ๋กค ์‹œ ๊ธฐ์กด ์˜์—ญ์€ ๊ณ ์ •๋œ ์ƒํƒœ์—์„œ ๋‹ค์Œ ์˜์—ญ์ด ๋‚˜ํƒ€๋‚˜๋Š” ํšจ๊ณผ ๋งŒ๋“ค๊ธฐ

์œ„์˜ ์˜ˆ์ œ์™€ ๊ฐ™์ด ๊ธฐ์กด ์ปจํ…์ธ  ์˜์—ญ์€ ๊ณ ์ •๋œ ์ƒํƒœ์—์„œ ๋‹ค์Œ ์˜์—ญ์ด ์˜ฌ๋ผ์˜ค๋Š” ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์•˜๋‹ค. ์Šคํฌ๋กค์„ ํ•˜๋‹ค ์ค‘๊ฐ„์— ๋ฉˆ์ถ”๋ฉด ์Šค๋ƒ…๋˜๋Š” ํšจ๊ณผ๊นŒ์ง€ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค.

์ฝ”๋“œ ์„ค๋ช…์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

HTML

  • container -> sticky ์†์„ฑ์„ ์‚ฌ์šฉํ•  ์š”์†Œ์˜ ๋ถ€๋ชจ. ํŽ˜์ด์ง€์˜ ์Šคํฌ๋กค ๊ฐ’์„ ๊ฐ€์งˆ ์š”์†Œ
  • sticky -> sticky๊ฐ€ ๋  ์š”์†Œ
  • item -> ํ•˜๋‚˜์˜ ์˜์—ญ. ์Šคํฌ๋กค ์‹œ height ๊ฐ’์ด 0์ด ๋  ์š”์†Œ
  • content -> item์˜ ์ž์‹. height 100vh๋ฅผ ์ฃผ์–ด ์Šคํฌ๋กค ํ•ด๋„ ๋ถ€๋ชจ๋ฅผ ๋”ฐ๋ผ๊ฐ€์ง€ ์•Š์Œ
<div class="container">
  <div class="sticky">
    <div class="item">
      <div class="content">
        content 1
      </div>
    </div>
    <div class="item">
      <div class="content">
        content 2
      </div>
    </div>
    <!-- ...์ดํ•˜ ์ƒ๋žต(item ์ด ๊ฐœ์ˆ˜: 5) -->
  </div>
</div>

CSS

  • container ์š”์†Œ์— ์Šคํฌ๋กค ํ•  ๋งŒํผ์˜ ๋†’์ด ๊ฐ’ ์ฃผ๊ธฐ
  • item์— height: 100% ์ฃผ๊ธฐ (GSAP์œผ๋กœ ์Šคํฌ๋กค ํ•˜๊ฒŒ ๋˜๋ฉด 0์œผ๋กœ ์ค„์–ด๋“œ๋Š” ํšจ๊ณผ๋ฅผ ์ค„ ์˜ˆ์ •)
  • ๊ฐ item์— z-index ๊ฐ’ ์ฃผ๊ธฐ(ํ˜„์žฌ absolute๋กœ ๊ฒน์ณ์ง„ ์ƒํƒœ)
  • item์— mask-image ์†์„ฑ ์ฃผ๊ธฐ (ํ•ด๋‹น ์†์„ฑ์ด ์žˆ์–ด์•ผ ์Šคํฌ๋กค ์‹œ ๋‹ค์Œ ์˜์—ญ์ด ๋ณด์—ฌ์ง)
  • content์—๋Š” height: 100vh ์ฃผ๊ธฐ! ๊ทธ๋ž˜์•ผ ๋ถ€๋ชจ ๋†’์ด์— ์˜ํ–ฅ ๋ฐ›์ง€ ์•Š์Œ
  .container {
    position: relative;
    height: 350vh; /* 101vh ์ด์ƒ์˜ ์›ํ•˜๋Š” ์Šคํฌ๋กค ๊ฐ’ ์ฃผ๊ธฐ */
  }
  .sticky {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow: hidden;
  }
  .item {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%; /* ์Šคํฌ๋กค ์‹œ height 0์œผ๋กœ ์ค„์–ด๋“ฆ */
    mask-image: radial-gradient(#fff, #000); /* ํ•„์ˆ˜ ์†์„ฑ ! ์—†์œผ๋ฉด ์Šคํฌ๋กค ํ•ด๋„ ๋‹ค์Œ ์˜์—ญ์ด ๋ณด์ด์ง€ ์•Š์Œ */
  }
  /* absolute๋กœ item ๋“ค์ด ๋ชจ๋‘ ๊ฒน์ณ์ง„ ์ƒํƒœ. ๋”ฐ๋ผ์„œ z-index๋ฅผ ์ฃผ๊ธฐ */
  .item:nth-child(1) {
    z-index: 4;
  }
  .item:nth-child(2) {
    z-index: 3;
  }
  .item:nth-child(3) {
    z-index: 2;
  }
  .item:nth-child(4) {
    z-index: 1;
  }
  .item:nth-child(5) {
    z-index: 0;
  }
  .content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100vh; /* ๋ถ€๋ชจ๋Š” 100% -> 0%๋กœ ์ค„์–ด๋“ค์ง€๋งŒ ์ž์‹์€ ์Šคํฌ๋กค ํ•ด๋„ ์˜์—ญ์ด ์ค„์–ด๋“ค์ง€ ์•Š์•„์•ผ ํ•˜๊ธฐ์— 100vh ๊ฐ’ ์ฃผ๊ธฐ */
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 5vw;
    font-weight: 700;
    color: #fff;
  }

JS

  • scrollTrigger์˜ trigger ๋Œ€์ƒ์€ container๋กœ ์ง€์ •
  • ์Šคํฌ๋กค ์‹œ์ž‘๊ณผ ๋์ง€์ ์€ ๊ฐ๊ฐ 0% 0% / 100% 100%๋กœ ์„ค์ •
  • scrub ์ง€์ •
  • snap ์„ค์ • (์˜ต์…˜ ์„ค๋ช…์€ ์ฃผ์„ ์ฐธ๊ณ )
  • ๋ฐ˜๋ณต๋ฌธ์„ ๋Œ๋ ค์„œ height: 0์ด ๋˜๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ค€๋‹ค. ๋‹จ, ๋งˆ์ง€๋ง‰ ์•„์ดํ…œ์€ height ๊ฐ’์ด 0์ด ๋˜๋ฉด ์•ˆ๋˜๊ธฐ์— ์ œ์™ธํ•˜๊ณ  ์„ค์ •ํ•œ๋‹ค.
const tl = gsap.timeline({
  scrollTrigger: {
    trigger: '.container',
    start: '0% 0%',
    end: '100%, 100%',
    scrub: 0,
    snap: {
      snapTo: 'labels', // ํƒ€์ž„๋ผ์ธ์—์„œ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๋ผ๋ฒจ์— ์Šค๋ƒ…
      duration: {min: 0.2, max: 1}, // ์ตœ์†Œ 0.2 ์ตœ๋Œ€ 1์ดˆ ๋™์•ˆ
      delay: 0.1, // ์Šค๋ƒ…์„ ํ•˜๊ธฐ ์ „ 0.1์ดˆ ๋™์•ˆ ์ง€์—ฐ
      // ease: 'power1.inOut' // ์Šค๋ƒ… ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ease (๊ธฐ๋ณธ์ ์œผ๋กœ 'power3')
    }
  }
});

const itemEls = document.querySelectorAll('.item');

itemEls.forEach(function(itemEl, index) {
  const lastIndex = itemEls.length - 1;

  if(index !== lastIndex) {
    tl.to(itemEl, {height: 0}, `a${index}`)
  }
});

๐Ÿ˜ฎ ์—ฌ๊ธฐ์— ๊ธ€์”จ ํšจ๊ณผ๋ฅผ ์ž…ํžˆ๊ณ  ์‹ถ๋‹ค๋ฉด?

์•„๋ž˜ ๋ฐ๋ชจ์™€ ๊ฐ™์ด ๊ธ€์ž๊ฐ€ ํ•œ์ชฝ์—์„œ๋Š” ๋‚˜ํƒ€๋‚˜๊ณ  ๋ฐ˜๋Œ€ํŽธ์—์„œ ์‚ฌ๋ผ์ง€๋Š” ํšจ๊ณผ๋„ ์ถ”๊ฐ€ํ•ด๋ณผ ์ˆ˜ ์žˆ๋‹ค.
ํ…์ŠคํŠธ ์˜์—ญ ๊ตฌ์กฐ ์ถ”๊ฐ€๋กœ ๊ธฐ์กด ํด๋ž˜์Šค๋ช…๋„ ์ˆ˜์ •ํ–ˆ๋‹ค(item -> slide)


HTML

  • ํ…์ŠคํŠธ ์˜์—ญ์€ slide์˜ ๋ถ€๋ชจ์ธ slide-area์™€ ํ˜•์ œ ๊ตฌ์กฐ(absolute๋กœ ๋ฐฐ์น˜ํ•  ์˜ˆ์ •)
  • ์™ผ์ชฝ ํ…์ŠคํŠธ ๊ฐœ์ˆ˜: 6 / ์˜ค๋ฅธ์ชฝ ํ…์ŠคํŠธ ๊ฐœ์ˆ˜: 5
<div class="container">
  <div class="sticky">
    <ul class="brand-list left">
      <li class="brand-item">Adidas</li>
      <li class="brand-item">Calvin Klein</li>
      <li class="brand-item">Hourglass</li>
      <li class="brand-item">Eilish</li>
      <li class="brand-item">The Outset</li>
      <li class="brand-item">MAC</li>
    </ul>
    <ul class="brand-list right" aria-hidden="true">
      <li class="brand-item">Calvin Klein</li>
      <li class="brand-item">Hourglass</li>
      <li class="brand-item">Eilish</li>
      <li class="brand-item">The Outset</li>
      <li class="brand-item">MAC</li>
    </ul>
    <div class="slide-area">
      <div class="slide">
        <div class="content"></div>
      </div>
	<!-- ...์ดํ•˜ ์ƒ๋žต(slide ์ด ๊ฐœ์ˆ˜: 5) -->
    </div>
  </div>
</div>

CSS

  • brand-list(ํ…์ŠคํŠธ ๊ฐ์‹ธ๋Š” ์š”์†Œ) z-index๋ฅผ slide๋ณด๋‹ค ๋†’๊ฒŒ ์„ค์ •ํ•œ๋‹ค.
  • ๊ทธ๋ฆฌ๊ณ , brand-list๋Š” absolute๋กœ ๋ฐฐ์น˜ํ•œ๋‹ค.
  • ์™ผ์ชฝ ํ…์ŠคํŠธ๋Š” transform-origin์„ left top์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.
  • ์˜ค๋ฅธ์ชฝ ํ…์ŠคํŠธ๋Š” transform-origin์„ right top์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.
  • ์™ผ์ชฝ ํ…์ŠคํŠธ๋Š” ์ฒซ ๋ฒˆ์งธ ํ…์ŠคํŠธ ์ œ์™ธํ•˜๊ณ ๋Š” ์ฒ˜์Œ์— ๋ณด์—ฌ์ง€๋ฉด ์•ˆ๋˜๊ธฐ์— scale(0) ๊ฐ’์„ ์ค€๋‹ค.

** transform-origin ์ด๋ž€?
transform์˜ ๊ธฐ์ค€์ ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ์†์„ฑ์œผ๋กœ ์ดˆ๊ธฐ ๊ฐ’์€ 50% 50% 0(x์ถ• y์ถ• z์ถ•) ์ด๋‹ค.

  .brand-list {
    z-index: 5; /* slide z-index ๋ณด๋‹ค ๋†’๊ฒŒ ์„ค์ •. ๊ทธ๋ž˜์•ผ ํ…์ŠคํŠธ๊ฐ€ ๋ณด์—ฌ์ง */
    position: absolute; /* ํ…์ŠคํŠธ ์˜์—ญ absolute ๋ฐฐ์น˜ */
    display: flex;
    flex-direction: column;
  }
  .brand-list.left {
    top: 0;
    left: 0.4rem;
  }
  .brand-list.right {
    top: calc(50dvh + 1.04167vw);
    right: 0.72917vw;
    align-items: flex-end;
  }
  .brand-item {
    font-family: "Inter", sans-serif;
    font-size: 10vw;
    font-weight: 700;
    letter-spacing: -0.07em;
    color: #fff;
    text-transform: uppercase;
    white-space: nowrap;
  }
  .brand-list.left .brand-item {
    padding-top: 1rem;
    line-height: .73;
    transform-origin: left top; /* transform ๊ธฐ์ค€์  left top์œผ๋กœ ์„ค์ • */
  }
  .brand-list.left .brand-item:not(:first-child) {
    /* ์™ผ์ชฝ์€ ์ฒซ ๋ฒˆ์งธ ํ…์ŠคํŠธ ์ œ์™ธํ•˜๊ณ  ์•ˆ๋ณด์ด๋Š” ์ƒํƒœ์—์„œ ์Šคํฌ๋กค ํ•  ๋•Œ๋งˆ๋‹ค ํ•˜๋‚˜ ์”ฉ ๋ณด์—ฌ์งˆ ์˜ˆ์ •์œผ๋กœ ๊ธฐ๋ณธ ์„ธํŒ…์œผ๋กœ scale 0 ์ ์šฉ*/
    transform: scale(0); 
  }
  .brand-list.right .brand-item {
    line-height: .79;
    transform-origin: right top; /* transform ๊ธฐ์ค€์  right top์œผ๋กœ ์„ค์ • */
  }

JS

  • ์™ผ์ชฝ ํ…์ŠคํŠธ์™€ ์˜ค๋ฅธ์ชฝ ํ…์ŠคํŠธ์— ๋Œ€ํ•ด ๊ฐ๊ฐ ๋ฐ˜๋ณต๋ฌธ์„ ๋Œ๋ฆฐ๋‹ค.

  • nth-child์— i ๊ฐ’์„ ๋„ฃ์„ ์˜ˆ์ •์œผ๋กœ i๋Š” 1๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋„๋ก ํ•œ๋‹ค.

  • ์™ผ์ชฝ ํ…์ŠคํŠธ ๊ฐœ์ˆ˜: 6 / ์˜ค๋ฅธ์ชฝ ํ…์ŠคํŠธ ๊ฐœ์ˆ˜: 5๋กœ ์™ผ์ชฝ์ด 1๊ฐœ ๋” ๋งŽ๋‹ค. ์™ผ์ชฝ ๋งจ ๋งˆ์ง€๋ง‰ ํ…์ŠคํŠธ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๊ฐ€ ์—†์–ด์•ผ ํ•˜๋ฏ€๋กœ ์กฐ๊ฑด๋ฌธ์„ ์™ผ์ชฝ์€ length ๊ธธ์ด ๋ณด๋‹ค ๋ฏธ๋งŒ(<) ์ด๋„๋ก ์„ค์ •ํ•˜๊ณ , ์˜ค๋ฅธ์ชฝ์€ ์ดํ•˜(<=) ์ด๋„๋ก ์„ค์ •ํ•œ๋‹ค.

  • ์™ผ์ชฝ ๋ฐ˜๋ณต๋ฌธ - ์‚ฌ๋ผ์กŒ๋‹ค๊ฐ€ ๋‚˜ํƒ€๋‚จ
    1) ์Šคํฌ๋กค ์‹œ i ๋ฒˆ์งธ ํ…์ŠคํŠธ๋Š” yPercent ๊ฐ’ ๋งŒํผ ์œ„์ชฝ์œผ๋กœ ์ด๋™ํ•œ๋‹ค.
    2) i + 1 ๋ฒˆ์งธ ํ…์ŠคํŠธ๊ฐ€ scale์ด 1์ด ๋˜๋ฉด์„œ ๋‚˜ํƒ€๋‚œ๋‹ค.
    3) i ๋ฒˆ ์งธ ๋’ค์— ์žˆ๋Š” ๋ชจ๋“  ํ…์ŠคํŠธ๋“ค์€ ๋‹ค์Œ ์Šคํฌ๋กค ์ค€๋น„๋ฅผ ์œ„ํ•ด yPercent ๊ฐ’ ๋งŒํผ ์œ„์ชฝ์œผ๋กœ ์ด๋™ํ•œ๋‹ค. ๋‚˜๋จธ์ง€ ํ…์ŠคํŠธ๋“ค์€ scale์ด 0์ธ ์ƒํƒœ๋ผ ์œ„์ชฝ์œผ๋กœ ์ด๋™ํ•ด๋„ ๊ทธ ๋ชจ์Šต์ด ํ™”๋ฉด์— ๋ณด์—ฌ์ง€์ง€ ์•Š๋Š”๋‹ค.

  • ์˜ค๋ฅธ์ชฝ ๋ฐ˜๋ณต๋ฌธ - ๋‚˜ํƒ€๋‚ฌ๋‹ค๊ฐ€ ์‚ฌ๋ผ์ง
    1) i ๋ฒˆ์งธ ํ…์ŠคํŠธ๋Š” scale์ด 0์ด ๋˜๋ฉด์„œ ์‚ฌ๋ผ์ง„๋‹ค.
    2) i ๋ฒˆ ์งธ ๋’ค์— ์žˆ๋Š” ๋ชจ๋“  ํ…์ŠคํŠธ๋“ค์€ yPercent ๊ฐ’ ๋งŒํผ ์œ„์ชฝ์œผ๋กœ ์ด๋™ํ•œ๋‹ค.

  • slide ์• ๋‹ˆ๋ฉ”์ด์…˜(์Šคํฌ๋กค์‹œ height 0 ๋˜๋Š”)๊ณผ ๋™์‹œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก label ์—ฐ๊ฒฐ a0, a1...
    a${i - 1} ์ธ ์ด์œ ๋Š” ํ…์ŠคํŠธ ๋ฐ˜๋ณต๋ฌธ์—์„œ i๊ฐ€ 1๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ! ์Šฌ๋ผ์ด๋“œ ๋ฐ˜๋ณต๋ฌธ์—์„œ๋Š” index๊ฐ€ 0๋ถ€ํ„ฐ ์‹œ์ž‘์ด๋‹ค.

for(let i = 1; i < $('.brand-list.left .brand-item').length; i++) {
  tl.to(`.brand-list.left .brand-item:nth-child(${i})`, {
    yPercent: `-${i}00`,
  }, `a${i - 1}`)
    .to(`.brand-list.left .brand-item:nth-child(${i + 1})`, {
    scale: 1
  }, `a${i - 1}`)
    .to(`.brand-list.left .brand-item:nth-child(n + ${i + 1})`, {
    yPercent:`-${i}00`,
  }, `a${i - 1}`)
}

for(let i = 1; i <= $('.brand-list.right .brand-item').length; i++) {
  tl.to(`.brand-list.right .brand-item:nth-child(${i})`, {
    scale: 0,
  }, `a${i - 1}`)
    .to(`.brand-list.right .brand-item:nth-child(n + ${i + 1})`, {
    yPercent:`-${i}00`,
  }, `a${i - 1}`)
}


์ง€์—ญ๋ณ„ ์‹œ๊ฐ„ ๊ตฌํ•˜๊ธฐ

Bennett & Clive ์‚ฌ์ดํŠธ๋Š” ํ‘ธํ„ฐ ์˜์—ญ์— ์‚ฌ๋ฌด์‹ค ์œ„์น˜์™€ ํ˜„์ง€ ์‹œ๊ฐ„์„ ๋‚˜ํƒ€๋‚ด๋Š” UI๊ฐ€ ์žˆ๋‹ค.
์ด ๋ถ€๋ถ„์„ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋Š”์ง€ ์ •๋ฆฌ๋ณด๊ณ ์ž ํ•œ๋‹ค.

ํ˜„์žฌ ์‹œ๊ฐ„ ํ™•์ธํ•˜๊ธฐ

new Date()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ€์ ธ์˜จ ํ˜„์žฌ ๋‚ ์งœ์™€ ์‹œ๊ฐ„์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ PC์— ์„ค์ • ๋œ ํ‘œ์ค€ ์‹œ๊ฐ„๋Œ€๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ‘œ์‹œ๋œ๋‹ค. ๋งŒ์•ฝ ์‚ฌ์šฉ์ž PC์— ์„ค์ •๋œ ์‹œ๊ฐ„์ด KST๋ผ๋ฉด 'new Date()'๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ํ˜„์žฌ ํ•œ๊ตญ ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ๋‚ ์งœ์™€ ์‹œ๊ฐ„์ด ํ‘œํ˜„๋œ๋‹ค. ํ•˜์ง€๋งŒ, ์‚ฌ์šฉ์ž PC์˜ ํ‘œ์ค€์‹œ๊ฐ„๋Œ€๊ฐ€ ํ•œ๊ตญ ํ‘œ์ค€์‹œ๊ฐ€ ์•„๋‹Œ ๋ฏธ๊ตญ์ด๋‚˜ ์บ๋‚˜๋‹ค์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋™๋ถ€ ํ‘œ์ค€์‹œ๋‚˜ ์ค‘๋ถ€ ํ‘œ์ค€์‹œ๋กœ ์„ค์ •์ด ๋˜์–ด ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น ํ‘œ์ค€์‹œ๊ฐ„๋Œ€์— ํ•ด๋‹นํ•˜๋Š” ๋‚ ์งœ์™€ ์‹œ๊ฐ„์ด ๋ณด์—ฌ์งˆ ๊ฒƒ์ด๋‹ค.

console.log(new Date()); // Tue Apr 09 2024 16:29:22 GMT+0900 (ํ•œ๊ตญ ํ‘œ์ค€์‹œ)

PC์˜ ํ‘œ์ค€์‹œ๊ฐ„๋Œ€ ์„ค์ •๊ณผ ๊ด€๊ณ„ ์—†์ด ํ•œ๊ตญ ์‹œ๊ฐ„ํ…์ŠคํŠธ ํ‘œ์‹œํ•˜๊ธฐ

// 1. ํ˜„์žฌ ์‹œ๊ฐ„(Locale) ๊ฐ€์ ธ์˜ค๊ธฐ
const current = new Date();

// 2. UTC ์‹œ๊ฐ„ ๊ณ„์‚ฐ
const utc = current.getTime() + (current.getTimezoneOffset() * 60 * 1000);

// 3. UTC to KST (UTC + 9์‹œ๊ฐ„)
const krTimeDiff = 9 * 60 * 60 * 1000;
const krCurrent = new Date(utc + (krTimeDiff));

console.log(krCurrent); // Tue Apr 09 2024 16:47:50 GMT+0900 (ํ•œ๊ตญ ํ‘œ์ค€์‹œ)

1) ํ˜„์žฌ ์‹œ๊ฐ„ (locale ์‹œ๊ฐ„) ๊ฐ€์ ธ์˜ค๊ธฐ

๋จผ์ € ํ˜„์žฌ ์‹œ๊ฐ„์„ ๊ฐ€์ ธ์˜จ๋‹ค. new Date()๋Š” ์‚ฌ์šฉ์ž PC์— ์„ค์ •๋œ ์‹œ๊ฐ„๋Œ€ ๊ธฐ์ค€์œผ๋กœ ์‹œ๊ฐ„์„ ํ‘œ์‹œํ•ด ์ค€๋‹ค.

const current = new Date();

2) UTC ์‹œ๊ฐ„ ๊ณ„์‚ฐ

getTime()

1970๋…„ 1์›” 1์ผ 00:00:00(UTC)์„ ๊ธฐ์ ์œผ๋กœ ํ˜„์žฌ ์‹œ๊ฐ„๊นŒ์ง€ ๊ฒฝ๊ณผ๋œ ๋ฐ€๋ฆฌ์ดˆ๋ฅผ ๋ฐ˜ํ™˜

const current = new Date();
const time = current.getTime();

console.log(current); // Tue Apr 09 2024 17:42:51 GMT+0900 (ํ•œ๊ตญ ํ‘œ์ค€์‹œ)
console.log(time); // 1712652171137

getTimezoneOffset()

ํ˜„์žฌ ์‚ฌ์šฉ์ž PC ์„ค์ • ์‹œ๊ฐ„๋Œ€๋กœ๋ถ€ํ„ฐ UTC ์‹œ๊ฐ„๊นŒ์ง€์˜ ์ฐจ์ด๋ฅผ ๋ถ„ ๋‹จ์œ„๋กœ ๋ฆฌํ„ด

์‹œ๊ฐ„๋Œ€ ์˜คํ”„์…‹์€ UTC์™€ ํ˜„์ง€ ์‹œ๊ฐ„์˜ ์ฐจ์ด (๋ถ„)์ž…๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ๋กœ์ปฌ ์‹œ๊ฐ„๋Œ€๊ฐ€ UTC๋ณด๋‹ค ๋’ค๋–จ์–ด์ ธ ์žˆ์œผ๋ฉด ์˜คํ”„์…‹์ด ์–‘์ˆ˜์ด๊ณ  ์•ž์—์žˆ์„ ๊ฒฝ์šฐ ์Œ์ˆ˜์ž„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‹œ๊ฐ„๋Œ€ UTC + 10 : 00 (์˜ค์ŠคํŠธ๋ ˆ์ผ๋ฆฌ์•„ ๋™๋ถ€ ํ‘œ์ค€์‹œ, ๋ธ”๋ผ๋””๋ณด์Šคํ† ํฌ ์‹œ๊ฐ„, ์ฐจ๋ชจ๋กœ ํ‘œ์ค€์‹œ)์˜ ๊ฒฝ์šฐ -600์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
- MDN

// ํ˜ธ์ŠคํŠธ ์žฅ์น˜์˜ ํ˜„์žฌ ์‹œ๊ฐ„๋Œ€ ์˜คํ”„์…‹ ๊ฐ€์ ธ ์˜ค๊ธฐ
const current = new Date();
const offset = current.getTimezoneOffset() / 60; // ๋‚˜๋ˆ„๊ธฐ 60: ๋ถ„ -> ์‹œ๊ฐ„ ๋‹จ์œ„๋กœ ๋ณ€๊ฒฝ 

console.log(offset); // -9

KST(Korea Standard Time)๋Š” UTC์— 9์‹œ๊ฐ„์„ ๋”ํ•œ ์‹œ๊ฐ„์ด๋‹ค. ์ฆ‰, UTC = KST - 9h ์ด๋‹ค.

UTC ์‹œ๊ฐ„์„ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•ด getTime()์™€ getTimezoneOffset() ํ•จ์ˆ˜์—์„œ ๋‚˜์˜จ ๋ถ„ ๋‹จ์œ„์˜ ์‹œ๊ฐ„์„ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋”ํ•ด์ฃผ์—ˆ๋‹ค.

์ด์ œ UTC๋Š” ํ˜„์žฌ ์‹œ๊ฐ„์„ UTC ์‹œ๊ฐ„์œผ๋กœ ๋ณ€ํ™˜ํ•œ ๋ฐ€๋ฆฌ์ดˆ ๊ฐ’์ด๋‹ค.

const utc = current.getTime() + (current.getTimezoneOffset() * 60 * 1000);

๐Ÿ˜ฅ ๋‹จ์œ„ ๊ณ„์‚ฐ ๋ฐฉ๋ฒ•์ด ํ—ท๊ฐˆ๋ ค์š”.

1์ดˆ = 1,000 ๋ฐ€๋ฆฌ์ดˆ
1๋ถ„ = 1000 * 60 = 60,000 ๋ฐ€๋ฆฌ์ดˆ
1์‹œ๊ฐ„ = 1000 * 60 * 60 = 3,600,000 ๋ฐ€๋ฆฌ์ดˆ

3) UTC to KST (UTC + 9์‹œ๊ฐ„)

ํ•œ๊ตญ ์‹œ๊ฐ„(KST)์€ UTC ์‹œ๊ฐ„๋ณด๋‹ค 9์‹œ๊ฐ„ ๋” ๋น ๋ฅด๋‹ค. 9์‹œ๊ฐ„์„ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜ํ•˜์˜€๋‹ค.

const krTimeDiff = 9 * 60 * 60 * 1000;

UTC ์‹œ๊ฐ„์„ ํ•œ๊ตญ ์‹œ๊ฐ„์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด UTC ๋ฐ€๋ฆฌ์ดˆ ๊ฐ’์— 9์‹œ๊ฐ„(๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜ํ•œ ๊ฐ’)์„ ๋”ํ•ด ์ฃผ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ , new Date()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, ๋ฐ€๋ฆฌ์ดˆ ๊ฐ’์„ date๋กœ ๋ณ€ํ™˜ํ•˜์˜€๋‹ค.

const krCurrent = new Date(utc + (krTimeDiff));

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด,ย ์‚ฌ์šฉ์ž ํ™˜๊ฒฝ์˜ ์‹œ๊ฐ„๋Œ€๊ฐ€ ์–ด๋–ป๊ฒŒ ์„ค์ •์ด ๋˜์–ด ์žˆ๋“ ย ์‚ฌ์šฉ์ž์—๊ฒŒ ํ•œ๊ตญ ์‹œ๊ฐ„์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค. krTimeDiff ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด์ค€๋‹ค๋ฉด ํ•œ๊ตญ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ๋‚˜๋ผ์˜ ์‹œ๊ฐ„๋„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋‹ค.

๋‚˜๋Š” ์ด ๋ฐฉ์‹์„ ์‘์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ์ง€์—ญ์˜ ์‹œ๊ฐ„๋„ ๊ตฌํ•˜์˜€๋‹ค.

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



CSS scrollbar-gutter ์†์„ฑ

๋กœ๋”ฉ ๋ชจ์…˜์ด ๋๋‚˜๊ณ  body ์š”์†Œ์— overflow: hidden ์†์„ฑ์„ ํ•ด์ œ ํ•˜์˜€๋Š”๋ฐ, ์Šคํฌ๋กค๋ฐ”๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹ค๊ฐ€ ์ƒ๊ฒจ ์Šคํฌ๋กค๋ฐ” ๋„ˆ๋น„๋งŒํผ ๋ทฐํฌํŠธ ๋„ˆ๋น„๊ฐ€ ๋ฐ”๋€Œ์–ด ํ™”๋ฉด์ด ์•ฝ๊ฐ„ ๋œ์ปน ๊ฑฐ๋ฆฌ๋Š” ๋Š๋‚Œ์ด ๋‚ฌ๋‹ค.

์•„๋ž˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด๋ณด์ž


์ด ๋ถ€๋ถ„์„ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์–ด์„œ ์ฐพ์•„ ๋ณด๋˜ ์ค‘ ์Šคํฌ๋กค๋ฐ” ๊ฑฐํ„ฐ(gutter)์˜ ์กด์žฌ์— ๋Œ€ํ•œ ์ œ์–ด๋ฅผ ํ•˜๋Š” CSS ์†์„ฑ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

CSS ์†์„ฑ์„ ์‚ฌ์šฉ ํ•˜๋ฉด ์ž‘์„ฑ์ž๊ฐ€ ์Šคํฌ๋กค ๋ง‰๋Œ€๋ฅผ ์œ„ํ•œ ๊ณต๊ฐ„์„ ์˜ˆ์•ฝํ•˜์—ฌ ์ฝ˜ํ…์ธ ๊ฐ€ ์ปค์งˆ ๋•Œ ์›์น˜ ์•Š๋Š” ๋ ˆ์ด์•„์›ƒ ๋ณ€๊ฒฝ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๋™์‹œ์— ์Šคํฌ๋กค์ด ํ•„์š”ํ•˜์ง€ ์•Š์„ ๋•Œ ๋ถˆํ•„์š”ํ•œ ์‹œ๊ฐ์  ์š”์†Œ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - MDN

html {
  /* viewport ์Šคํฌ๋กค๋ฐ”์— ์—ฌ๋ฐฑ์„ ์›ํ•˜๋Š” ๊ฒฝ์šฐ root"(html) ์š”์†Œ์— ํ• ๋‹นํ•ด์•ผํ•œ๋‹ค. */
  scrollbar-gutter: stable;
}

์œ„ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€๋‹ค. ๋‹ค์‹œ ํ•œ๋ฒˆ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด๋ณด์ž.


stable์€ ์Šคํฌ๋กค๋ฐ”๊ฐ€ ์—†์„ ๊ฒฝ์šฐ์—๋„ ์Šคํฌ๋กค๋ฐ” ์˜์—ญ์„ ์ƒ์„ฑํ•˜์—ฌ, ์Šคํฌ๋กค๋ฐ” ์œ ๋ฌด์— ์ƒ๊ด€์—†์ด ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ  ๋„ˆ๋น„๊ฐ€ ๋™์ผํ•œ ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

.box {
  scrollbar-gutter: stable;
}


both-edges๋Š” stable๊ณผ ํ•จ๊ป˜ ์“ฐ์ด๋Š” ์˜ต์…˜์œผ๋กœ, ์Šคํฌ๋กค๋ฐ”๊ฐ€ ์œ„์น˜ํ•œ ์˜ค๋ฅธ์ชฝ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์™ผ์ชฝ์—๋„ ๋™์ผํ•œ ํฌ๊ธฐ์˜ ๊ณต๊ฐ„์„ ์ƒ์„ฑํ•ด ์คŒ์œผ๋กœ์จ, ์ขŒ์šฐ ์ •๋ ฌ์ด ํ‹€์–ด์ ธ ๋ณด์ด์ง€ ์•Š๋„๋ก ๋„์™€์ค€๋‹ค.
(์ฆ‰, ๋‚ด์šฉ์ด ์ค‘์•™์— ์˜ค๋„๋ก ์ƒ์ž ์–‘์ชฝ์— ๋Œ€์นญ ๊ฐ„๊ฒฉ์„ ์ถ”๊ฐ€)

.box {
  scrollbar-gutter: stable both-edges;
}

์ง€์› ๋ฒ”์œ„

์•„์‰ฝ๊ฒŒ๋„ scrollbar-gutter ์†์„ฑ์€ Safari ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ง€์›๋˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋Ÿฌ๋ฏ€๋กœ scrollbar-gutter ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด, ๋ธŒ๋ผ์šฐ์ €๋ณ„ ๋Œ€์‘์„ ์ถฉ๋ถ„ํžˆ ๊ณ ๋ คํ•œ ํ›„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.



์ฐธ๊ณ  ์‚ฌ์ดํŠธ
https://agal.tistory.com/213
https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-gutter
https://velog.io/@kh8640/DropDown-2
https://wit.nts-corp.com/2024/02/06/6879
https://hianna.tistory.com/451

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

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