๐ป ESE Agency ํด๋ก ์ฝ๋ฉ
์ค์ 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
.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
์ด๋ฏธ์ง์ ์ด ๊ฐ์์ ์ด๋ฒคํธ ์งํ๋ฅ ์ ๊ณฑํ๋ฉด ์คํฌ๋กค ํ ๋ ๋ง๋ค 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 ํด๋์ค ์ถ๊ฐ
});
}
})
๐ ์์ฑ์ฝ๋
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-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);
}
})
});
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 ์์ฑ ์ถ๊ฐ๋ ํด์ผํ๋๋ฐ ํจ๊ณผ๊ฐ ์ง์ ๋๋ ์์์ ์์ฑ์ ์๋์ ๊ฐ๋ค.
1) ์ง์ ์์
- 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
: ์์ฐจ ์ ํ ํ์ฑํdata-swiper-parallax-x
: x์ถ ๋ฐฉํฅdata-swiper-parallax-y
: y์ถ ๋ฐฉํฅdata-swiper-parallax-scale
: "๋นํ์ฑ"(ํ์ฑ ์ฌ๋ผ์ด๋๊ฐ ์๋) ์ํ์ผ ๋ ์์ฐจ ์์์ ํฌ๊ธฐ ๋น์จdata-swiper-parallax-opacity
: "๋นํ์ฑ"(ํ์ฑ ์ฌ๋ผ์ด๋๊ฐ ์๋) ์ํ์ผ ๋ ์์ฐจ ์์์ ๋ถํฌ๋ช
๋data-swiper-parallax-duration
: ์์ฐจ ์์์ ๋ํ ์ฌ์ฉ์ ์ง์ ์ ํ ๊ธฐ๊ฐ๊ฐ๋ฐ์ ๋๊ตฌ๋ก ์ ์ฉ์ด ์ ๋์๋์ง ํ์ธ ๊ฐ๋ฅํ๋ค.
ํ์ฌ 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);
}
})
});
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๋ถํฐ ์์
onReverseComplete
๐ ์์ฑ์ฝ๋
ESE Agency ์ฌ์ดํธ๋ svh ๋จ์๋ฅผ ์ฌ์ฉํ์ฌ ์คํ์ผ์ ์ค ๋ถ๋ถ๋ค์ด ๋ง์๋๋ฐ, ๊ทธ๋์ ์ด๋ฒ์ CSS ๋ทฐํฌํธ ๋จ์์ ๋ํด ์ ๋ฆฌํด๋ณด๊ณ ์ ํ๋ค.
vh ๋จ์๋ ์ ์ ์๊ฒ ๋ณด์ด๋ ์์ญ์ ํฌ๊ธฐ๊ฐ์ ์ฐธ์กฐํ๊ธฐ ์ํด 2015๋ ์ ์ถ๊ฐ๋์๋ค. ํ์ง๋ง ๋ธ๋ผ์ฐ์ ์คํฌ๋กค ์ ์ฃผ์์ฐฝ์ ํฌ๊ธฐ ๋ณํ์ ๋ฐ๋ฅธ ๋ทฐํฌํธ ์์ญ์ ํฌ๊ธฐ ๋ณํ์ ๊ด๋ จ๋ ๋ชจํธํจ ๋๋ฌธ์, 2021๋ dvh, lvh, svh ๋จ์๊ฐ ์๋ก ์ถ๊ฐ๋์๋ค.
๋ง์ฝ % ๋จ์๋ก ์์ ๋์ด๋ฅผ ํ๋ฉด(๋ทฐํฌํธ)์ ๊ฝ ์ฑ์ฐ๋๋ก ์ค์ ํ๋ ค๋ฉด, ๋ถ๋ชจ ์์๋ค์ ๋์ด๋ฅผ ๋ชจ๋ 100%๋ก ์ค์ ํด์ค์ผ ํ๋ค. ํ์ง๋ง vh ๋จ์๋ฅผ ์ฌ์ฉํ๋ฉด ๋ถ๋ชจ ์์์ ๋์ด์ ๊ด๊ณ ์์ด, ๋ทฐํฌํธ์ ๋์ด๋ก ๋ฐ๋ก ์ ์ฉํ ์ ์๋ค.
vh๋ ์ฃผ์์ฐฝ์ด ์์์ก์ ๋์ ๋ทฐํฌํธ ๋์ด๊ฐ ๋ฐ์๋๋ค. ๊ทธ๋์ ํน์ ํ์ด์ง๋ฅผ ์ฒ์ ์ ์ํด์ ๋ธ๋ผ์ฐ์์ ์ฃผ์์ฐฝ์ด ์ปค์ง ์ํ์์๋ 100vh๋ก ์ค์ ํ ์์๊ฐ ์๋ ค์ ธ์ ๋ณด์ธ๋ค.
vh๋ ๋ธ๋ผ์ฐ์ ์์ฒด UI์ ํฌ๊ธฐ ๋ณํ๋ฅผ ๋ฐ์ํ์ง ์๋๋ค.
์ด๋ฏธ์ง ์ถ์ฒ: GoogleIO 2022
lvh๋ vh์ ๋์ผํ๊ฒ ์ฃผ์์ฐฝ์ ํฌ๊ธฐ๊ฐ ์ถ์๋์ด ์ ์ ์๊ฒ ๋ณด์ด๋ ์์ญ์ด ์ ์ผ ํด ๋๋ฅผ ๋ฐ์ํ๋ค.
svh๋ ์ฃผ์์ฐฝ์ ํฌ๊ธฐ๊ฐ ์ปค์ ธ์ ์ ์ ์๊ฒ ๋ณด์ด๋ ์์ญ์ด ์ ์ผ ์์ ๋์ ๋์ด๋ฅผ ๋ฐ์ํ๋ค.
dvh๋ ๋ธ๋ผ์ฐ์ ์์ฒด UI์ ํฌ๊ธฐ๊ฐ ๋ฐ๋ ๋๋ง๋ค ์๋ก์ด ๊ฐ์ผ๋ก ๊ฐฑ์ ๋์ด, ์ค์ ์ ์ ๊ฐ ๋ณด๋ ์์ญ์ ํฌ๊ธฐ๋ฅผ ํญ์ ๋ฐ์ํ ์ ์๋ค.
๊ฐ ์์์ transition-delay๋ฅผ ์ฃผ์ด ํจ๊ณผ๋ฅผ ์ฃผ์ด์ผ ํ๋๋ฐ ๋ฆฌ์ฌ์ด์ง ๋ ๋๋ง๋ค ๋ ์ด์์ ๋ฐฐ์น๊ฐ ๋ณ๊ฒฝ๋์ด delay ๊ฐ์ ๋์ ์ผ๋ก ๊ณ์ ๋ณ๊ฒฝํด์ผ๋ง ํ๋ค. ๋ฑ์ฐจ์์ด์ ์ด์ฉํด์ ํด๊ฒฐ ํ๋๋ฐ ๊ณต์์ ๋ ๊น๋จน์ ๊ฒ์ ๋๋นํด์ ์ ๋ฆฌํด๋ณด๊ณ ์ ํ๋ค.
a + b(n - 1) = a + (bn - 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
ESE Agency ์ฌ์ดํธ๋ hover ํจ๊ณผ๋ฅผ @media(hover: hover) ์์ ๋ชจ๋ ์ ์ธํ๋ค. ๋ฏธ๋์ด์ฟผ๋ฆฌ hover์ pointer๋ ์ด๋ฒ์ ์๋กญ๊ฒ ์๊ฒ๋ ๋ถ๋ถ์ด๋ผ ์ ๋ฆฌํด๋ณด์๋ค.
์ฐ์ hover๋ ํน์ ์์์ ๋ง์ฐ์ค๋ฅผ ์ฌ๋ ธ์ ๋ ํ๋์ ์ทจํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
a:hover {
color: #fff;
background-color: #000;
}
ํฐ์น์คํฌ๋ฆฐ์ ์ฌ์ฉํ๋ ๋ชจ๋ฐ์ผ์ ๊ธฐ๋ณธ์ ์ผ๋ก hover ๋์์ด ์์ง๋ง ๋ชจ๋ฐ์ผ์์๋ ์์ฃผ ์ ๊น๋์ ํจ๊ณผ๊ฐ ๋ํ๋๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. ํฐ์น์คํฌ๋ฆฐ์์ ๊พน ๋๋ฅด๋ ๋์ ๋ฑ๋ hover๋ก ์ธ์ํ๊ธฐ์ ๋ง์ฐ์ค ๋ฑ์ ์ฅ์น๊ฐ ์์ ๋ ์๋์น ์์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ์ฌํ๊ธฐ๋ ํ๋ค.
์ด๊ฒ์ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ์ด ๋ฐ๋ก ๋ฏธ๋์ด์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. hover์ pointer ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํ๋ฉด ๋ชจ๋ฐ์ผ๊ณผ ๋ฐ์คํฌํฑ์ ๊ตฌ๋ถํ๋ ๊ฒ์ด ์ด๋ ์ ๋ ๊ฐ๋ฅํ๋ค.
hover ๋ฏธ๋์ด ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ ํฌ์ธํ
์ฅ์น๋ก ํ์ด์ง์ ์์๋ฅผ ํธ๋ฒ๋ง ํ ์ ์๋์ง ์ฌ๋ถ๋ฅผ ํ๋จํ๋ ๋ฐ ์ฌ์ฉ๋๋ค. ๊ธฐ์กด CSS์ ๊ฐ์ ์ ํ์ :hover
๋ ํธ๋ฒ๊ฐ ๋ฐ์ํ๋ ๊ฒฝ์ฐ์ ์คํ์ผ์ ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋์ง๋ง, ๋ฏธ๋์ด์ฟผ๋ฆฌ์ hover๋ ์ฅ์น์ ์
๋ ฅ ๋ฉ์ปค๋์ฆ์ ๊ธฐ๋ฐ์ผ๋ก ํธ๋ฒ๊ฐ ๋ฐ์ ๊ฐ๋ฅํ์ง ์ฌ๋ถ๋ฅผ ํ๋จํ ์ ์๋ค.
hover ๋ฏธ๋์ด๋ ๋ค์์ ๋ ๊ฐ์ง ๊ฐ์ ์ง์ํ๋ค.
none
1) ์ฃผ ํฌ์ธํ
์ฅ์น๊ฐ ์๋ ๊ฒฝ์ฐ
2) ์ฃผ ํฌ์ธํ
์ฅ์น๊ฐ hover๋ฅผ ์ง์ํ์ง ์๋ ๊ฒฝ์ฐ
3) hover๊ฐ ๊ฐ๋ฅํ์ง๋ง ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ด ์๋ ๋ถํธํ ๋ฐฉ๋ฒ์ผ๋ก hover ํ๋ ๊ฒฝ์ฐ (ex: ํฐ์น์คํฌ๋ฆฐ์ long tab์ hover๋ก ๊ฐ์ฃผ๋ ์ ์์ผ๋ ์ผ๋ฐ์ ์ธ hover ๋์์ ์๋)
hover
๊ธฐ๋ณธ ํฌ์ธํ
์ฅ์น๊ฐ ํน์ ์๋ฆฌ๋จผํธ ์๋ก ์ฝ๊ฒ hover ํ ์ ์๋ ๊ฒฝ์ฐ
๋ํ์ ์ผ๋ก ๋ง์ฐ์ค์ ๊ฐ์ ์ฅ์น๋ฅผ ์๊ฐํ ์ ์๋ค.
** ํฐ์น์คํฌ๋ฆฐ์ด ์ฃผ ํฌ์ธํ
์ฅ์น์ด์ง๋ง ๋ง์ฐ์ค๋ฅผ ๋์์ ์ง์ํ๋ ๊ฒฝ์ฐ
๋น๋ก ๋ง์ฐ์ค๋ฅผ ํตํด์ hover ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์์ง๋ง, ์ฃผ ํฌ์ธํ
์ ํฐ์น์คํฌ๋ฆฐ์ด๋ฏ๋ก hover : none์ด ๋๋ค.
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