#
27.마우스트레일
#
목차
1. 마우스 트레일 구현 1.1. 기본예제 1.2. 실전예제
2. GSAP을 적용한 마우스 트레일 기능 만들기 2.1. 마우스트레일 2.2. 특정요소에 효과 추가 2.3. 패럴렉스 효과
#
1. 마우스 트레일 구현
#
1.1. 기본예제
- html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<title>샘플</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<script src="main.js" defer></script>
</head>
<body>
<div class="followAnimation">👻</div>
</body>
</html>
- css
@import url(https://qwerewqwerew.github.io/source/style/reset.css);
html,
body {
width: 100%;
height: 100%;
}
.followAnimation {
position: fixed;
top: 0;
left: 0;
will-change: transform;
font-size: 5rem;
}
- js
// 커서를 따라다니는 요소 가져오기
const el = document.querySelector('.followAnimation');
// 마우스 좌표
let mouseX = 0;
let mouseY = 0;
// 커서를 따라다니는 요소의 좌표
let currentX = 0;
let currentY = 0;
// 마우스 이동 시
document.body.addEventListener('mousemove', (event) => {
// 마우스 좌표 저장
mouseX = event.clientX;
mouseY = event.clientY;
});
tick();
function tick() {
// 애니메이션 프레임 지정
requestAnimationFrame(tick);
// 따라오는 요소 좌표에 마우스 좌표를 지연시켜 반영
currentX += (mouseX - currentX) * 0.1;
currentY += (mouseY - currentY) * 0.1;
// 지연된 좌표를 위치에 반영
el.style.transform = `translate(${currentX}px, ${currentY}px)`;
}
#
1.2. 실전예제
기본세팅
- html 작성
index.html
<!-- .btns>.container>.btns_menu>button.btn*5 -->
<div class="btns">
<div class="container">
<div class="btns_menu"><button class="btn home" data-link="#home">home</button><button class="btn about" data-link="#about">about</button><button class="btn skills" data-link="#skills">skills</button><button class="btn works" data-link="#works">works</button><button class="btn contact" data-link="#contact">contact</button></div>
<!-- //.btns_menu -->
<!-- btn.btns_toggle-btn>sapn.material-symbols-outlined -->
<button class="btns_toggle-btn">
<i class="fa-solid fa-bars"></i>
</button>
</div>
<!-- //.container -->
</div>
<!-- //.btns -->
<!-- section#home>.wrapper>h2+h3+p -->
<section id="home">
<div class="wrapper">
<h2>HOME</h2>
<h3>Lorem ipsum dolor sit</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem, doloremque quae illum provident ipsum maxime recusandae consectetur, explicabo eaque voluptatibus amet eos sapiente architecto pariatur sed ad ratione commodi sequi.</p>
</div>
</section>
<!-- //#home -->
<section id="about">
<div class="wrapper">
<h2>about</h2>
<h3>Lorem ipsum dolor sit</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem, doloremque quae illum provident ipsum maxime recusandae consectetur, explicabo eaque voluptatibus amet eos sapiente architecto pariatur sed ad ratione commodi sequi.</p>
</div>
</section>
<!-- //#about -->
<section id="skills">
<div class="wrapper">
<h2>skills</h2>
<h3>Lorem ipsum dolor sit</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem, doloremque quae illum provident ipsum maxime recusandae consectetur, explicabo eaque voluptatibus amet eos sapiente architecto pariatur sed ad ratione commodi sequi.</p>
</div>
</section>
<!-- //#skills -->
<section id="works">
<div class="wrapper">
<h2>works</h2>
<h3>Lorem ipsum dolor sit</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem, doloremque quae illum provident ipsum maxime recusandae consectetur, explicabo eaque voluptatibus amet eos sapiente architecto pariatur sed ad ratione commodi sequi.</p>
</div>
</section>
<!-- //#works -->
<section id="contact">
<div class="wrapper">
<h2>contact</h2>
<h3>Lorem ipsum dolor sit</h3>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem, doloremque quae illum provident ipsum maxime recusandae consectetur, explicabo eaque voluptatibus amet eos sapiente architecto pariatur sed ad ratione commodi sequi.</p>
</div>
</section>
<!-- //#contact -->
<!-- button.arrow-up>span.material-symbols-outlined -->
<button class="arrow-up"><i class="fa-solid fa-circle-up"></i></button>
- css 작성
common.css
@import url(https://qwerewqwerew.github.io/source/style/reset.css);
/* ====================================
======= common ======
==================================== */
body {
background-color: lightyellow;
transition: all 1s;
}
/* modify */
.btns_menu {
display: flex;
}
button {
background-color: #9ad7d9;
border: transparent;
padding: 1rem;
display: block;
cursor: pointer;
}
.btns {
position: fixed;
width: 100%;
margin: auto;
z-index: 100;
}
.btns .container {
position: relative;
max-width: 900px;
padding: 20px;
background-color: transparent;
transition: 0.3s;
margin: auto;
display: flex;
justify-content: center;
border-radius: 0 0 10px 10px;
}
.btns .container.bgshow {
padding: 20px;
background-color: #9ad7d9;
}
.arrow-up {
position: fixed;
bottom: 50px;
right: 50px;
width: 70px;
height: 70px;
color: rgb(255, 255, 255);
background-color: rgb(33, 112, 231);
border-radius: 50%;
text-align: center;
opacity: 0;
z-index: 200;
padding: 0;
}
.arrow-up span {
line-height: 70px;
font-size: 30px;
}
.arrow-up.visible {
opacity: 1;
}
section {
width: 100%;
height: 100vh;
padding-top: 88px;
}
.wrapper {
max-width: 1200px;
margin: auto;
text-align: center;
}
h3 {
font-size: 24px;
}
section {
width: 100%;
height: 100vh;
padding-top: 88px;
}
.wrapper {
max-width: 1200px;
margin: auto;
text-align: center;
}
h3 {
font-size: 24px;
}
.btns_toggle-btn {
position: absolute;
top: 10px;
right: 32px;
color: #193f40;
transition: 0.3s;
display: none;
}
.btns_toggle-btn span {
font-size: 24px;
}
@media screen and (max-width: 700px) {
body .btns .container.bgshow {
backdrop-filter: blur(10px);
background-color: #9ad7d955; /* filter: blur(10px); */
}
.btns.container {
background-color: #9accd9;
border-radius: 0;
padding: 24px;
}
.btns_toggle-btn {
display: block;
}
.btns_menu {
flex-direction: column;
width: 100%;
align-items: center;
justify-content: center;
padding-top: 20px;
display: none;
}
.btns_menu.open {
display: flex;
}
.btns_menu button {
display: block;
margin: 10px auto;
width: 80%;
}
}
- js작성
/* ==========
scroll nav
========== */
(() => {
const navContainer = document.querySelector('.btns .container');
document.addEventListener('scroll', function () {
if (window.scrollY > 60) {
navContainer.classList.add('bgshow');
} else {
navContainer.classList.remove('bgshow');
}
});
})();
/* ==========
up btn
========== */
(() => {
const arrowUp = document.querySelector('.arrow-up');
document.addEventListener('scroll', function () {
if (window.scrollY > 500) {
arrowUp.classList.add('visible');
} else {
arrowUp.classList.remove('visible');
}
});
})();
/* ==========
nav anchor
========== */
(() => {
/* ==========
scroll nav
========== */
(() => {
const navContainer = document.querySelector('.btns .container');
document.addEventListener('scroll', function () {
if (window.scrollY > 60) {
navContainer.classList.add('bgshow');
} else {
navContainer.classList.remove('bgshow');
}
});
})();
/* ==========
up btn
========== */
(() => {
const arrowUp = document.querySelector('.arrow-up');
const home = document.querySelector('#home');
const btnsMenu = document.querySelector('.btns_menu');
const btnsToggleBtn = document.querySelector('.btns_toggle-btn');
document.addEventListener('scroll', function () {
if (window.scrollY > 500) {
arrowUp.classList.add('visible');
} else {
arrowUp.classList.remove('visible');
}
});
arrowUp.addEventListener('click', function () {
home.scrollIntoView({ behavior: 'smooth' });
});
/* add */
btnsMenu.addEventListener('click', (event) => {
const target = event.target;
const link = target.dataset.link;
console.log(link);
if (link == null) {
return;
}
const scrollTo = document.querySelector(link);
console.log(scrollTo);
scrollTo.scrollIntoView({ behavior: 'smooth', block: 'start' });
btnsMenu.classList.remove('open');
});
btnsToggleBtn.addEventListener('click', function (e) {
e.preventDefault();
btnsMenu.classList.toggle('open');
});
})();
여기까지 실행화면 실행화면
- html 추가 body 바로 다음 작성
<!-- .line.horizontal+.line.vertical+.target+span.tag -->
<div class="line horizontal"></div>
<div class="line vertical"></div>
<div class="target"></div>
<span class="tag">HI</span>
- css 추가
/* ====================================
======= here ======
==================================== */
.line {
position: absolute;
background-color: #9ad7d9;
}
.horizontal {
width: 100%;
height: 1px;
top: 50%;
}
.vertical {
width: 1px;
height: 100%;
left: 50%;
}
.target {
position: absolute;
width: 60px;
height: 60px;
border: solid 1px #9ad7d9;
border-radius: 50%;
transform: translate(-50%, -50%);
}
.tag {
position: absolute;
color: #9ad7d9;
top: 50%;
left: 50%;
font-size: 38px;
transform: translate(20px, 20px);
margin: 20px;
}
- js 작성
각각의 요소를 선택해서 변수에 할당한다.
const horizontal = document.querySelector('.horizontal');
const vertical = document.querySelector('.vertical');
const target = document.querySelector('.target');
const tag = document.querySelector('.tag');
타겟의 크기는 바뀔수있음을 감안해서getBoundingClientRect()
메서드를 이용한다
속성을 가져와서 폭과 높이의 반을 변수에 할당하고 스타일을 이용해 좌표에서 폭의 반을 빼줘서 타겟이 마우스의 중앙에 일치하도록 설정한다.
값을 입력할 때는 백틱키 ``를 이용해서 입력한다.
addEventListener('load', function () {
const targetRect = target.getBoundingClientRect();
//console.log(targetRect);
const targetHalfWidth = targetRect.width / 2;
const targetHalfHeight = targetRect.height / 2;
document.addEventListener('mousemove', (event) => {
const x = event.clientX;
const y = event.clientY;
console.log(x + 'px' + y + 'px');
horizontal.style.transform = `translateY(${y}px)`;
});
});
여기까지 실행화면 실행화면
마우스 트레일 기능 만들기
- html 수정
line 부터 tag 까지를 .mouse 요소로 랩핑한다
<div class="mouse">
<div class="line horizontal"></div>
<div class="line vertical"></div>
<div class="target"></div>
<span class="tag">HI</span>
</div>
- css 추가
body {
overflow-x: hidden;
}
.mouse {
position: fixed;
height: 100%;
width: 100%;
z-index: 99;
}
.line {
position: absolute;
background-color: #9ad7d9;
}
.horizontal {
width: 100%;
height: 1px;
top: 50%;
}
.vertical {
width: 1px;
height: 100%;
left: 50%;
}
.target {
position: absolute;
width: 60px;
height: 60px;
border: solid 1px #9ad7d9;
border-radius: 50%;
transform: translate(-50%, -50%);
}
.tag {
position: absolute;
color: #9ad7d9;
top: 50%;
left: 50%;
font-size: 38px;
transform: translate(20px, 20px);
margin: 20px;
}
- js 추가
let curX = 0;
let curY = 0;
let mouseX = 0;
let mousey = 0;
function animate() {
requestAnimationFrame(animate);
curX += (mouseX - curX) * 0.1;
curY += (mouseY - curY) * 0.1;
horizontal.style.transform = `translateY(${curY}px)`;
vertical.style.transform = `translateX(${curX}px)`;
target.style.transform = `translate(${curX - targetHalfWidth}px, ${curY - targetHalfHeight}px)`;
tag.style.transform = `translate(${curX}px, ${curY}px)`;
tag.innerHTML = `${Math.ceil(curX)}px ${Math.ceil(curY)}px`;
}
animate();
#
2. GSAP을 적용한 마우스 트레일 기능 만들기
#
2.1. 마우스트레일
html,css 의 변경없이 스크립트만 수정한다.
const horizontal = document.querySelector('.horizontal');
const vertical = document.querySelector('.vertical');
const target = document.querySelector('.target');
const tag = document.querySelector('.tag');
document.addEventListener('mousemove', (event) => {
mouseX = event.clientX;
mouseY = event.clientY;
console.log(`${mouseX}px, ${mouseY}px`);
});
addEventListener('load', function () {
let mouseX = 0;
let mouseY = 0;
document.addEventListener('mousemove', function (event) {
mouseX = event.clientX;
mouseY = event.clientY;
gsap.to('.horizontal', 5, { y: mouseY });
gsap.to('.vertical', 0.5, { x: mouseX });
gsap.to('.target', 0.5, { x: mouseX, y: mouseY });
gsap.to('.tag', 0.5, { x: mouseX, y: mouseY });
tag.innerHTML = `${Math.ceil(mouseX)}px ${Math.ceil(mouseY)}px`;
});
});
#
2.2. 특정요소에 효과 추가
- body 자식으로 .cursor 추가
<body>
<div class="cursor"></div>
</body>
- section 하위 p에 클래스 추가
<section id="home">
<div class="wrapper">
<h2>HOME</h2>
<h3>Lorem ipsum dolor sit</h3>
<p class="style1">Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem, doloremque quae illum provident ipsum maxime recusandae consectetur, explicabo eaque voluptatibus amet eos sapiente architecto pariatur sed ad ratione commodi sequi.</p>
</div>
</section>
... 생략
<p class="style2"></p>
... 생략
<p class="style3"></p>
... 생략
<p class="style4"></p>
... 생략
<p class="style5"></p>
... 생략
- css 작성
section p {
font-size: 3rem;
}
.cursor {
position: fixed;
left: 0;
top: 0;
width: 50px;
height: 50px;
border-radius: 50%;
border: 3px solid #131313;
z-index: 999999999999;
background-color: rgba(255, 255, 255, 0.1);
user-select: none;
pointer-events: none;
transition: background-color 0.2s, border-color 0.2s, border-radius 0.2s, transform 0.6s;
cursor: none;
}
.cursor.style1 {
background-color: rgba(255, 165, 0, 0.4);
border-color: orange;
}
.cursor.style2 {
background-color: rgb(140, 0, 255, 0.4);
border-color: rgb(140, 0, 255);
transform: rotateY(720deg) scale(2);
backdrop-filter: blur(2px);
}
.cursor.style2:after {
content: 'click';
position: absolute;
color: rgb(140, 0, 255);
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.cursor.style3 {
background-color: rgb(0, 47, 255, 0.4);
border-color: rgb(0, 47, 255);
transform: scale(1.5) rotate(45deg);
border-radius: 10px;
}
.cursor.style4 {
background-color: rgb(121, 235, 197, 0.4);
border-color: rgb(121, 235, 197);
transform: scale(10) rotateY(360deg);
}
.cursor.style5 {
background-color: rgb(255, 0, 85, 0.4);
border-color: rgb(255, 0, 85);
transform: rotateY(720deg) scale(0.1);
}
.cursor.style6 {
background-color: rgb(255, 81, 0, 0.4);
border-color: rgb(255, 81, 0);
transform: scale(1.5) rotate(-720deg);
border-radius: 0;
}
- 스크립트 작성
const target = document.querySelector('.cursor');
let mouseX = 0;
let mouseY = 0;
window.addEventListener('mousemove', function (e) {
console.log(e.clientX);
console.log(e.clientY);
mouseX = e.clientX - 25 + 'px';
mouseY = e.clientY - 25 + 'px';
gsap.to(target, 0.5, { left: mouseX, top: mouseY });
});
document.querySelectorAll('section p').forEach((element) => {
let style = element.getAttribute('class');
element.addEventListener('mouseover', () => {
document.querySelector('.cursor').classList.add(style);
});
element.addEventListener('mouseout', () => {
document.querySelector('.cursor').classList.remove(style);
});
});
#
2.3. 패럴렉스 효과
1.
<div class="wrap">
<span data-value="-15"></span>
<span data-value="5"></span>
<span data-value="30"></span>
<span data-value="-5"></span>
<span data-value="15"></span>
<h2>Parallax effect</h2>
</div>
body {
margin: 0;
height: 100vh;
background-color: #bd1060;
overflow: hidden;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: sans-serif;
}
.wrap {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.wrap h2 {
position: relative;
font-size: 100px;
color: white;
z-index: 2;
text-align: center;
}
.wrap span {
position: absolute;
height: 20px;
width: 20px;
border-radius: 100%;
}
.wrap span:nth-child(1) {
top: 70%;
left: 70%;
background: blue;
z-index: 3;
}
.wrap span:nth-child(2) {
top: 60%;
left: 80%;
background: yellow;
z-index: 3;
}
.wrap span:nth-child(3) {
top: 40%;
left: 60%;
background: green;
z-index: 3;
}
.wrap span:nth-child(4) {
top: 70%;
left: 40%;
background: red;
z-index: 3;
}
.wrap span:nth-child(5) {
top: 40%;
left: 30%;
background: purple;
z-index: 3;
}
document.addEventListener('mousemove', parallax);
function parallax(e) {
this.querySelectorAll('.wrap span').forEach((el) => {
const position = el.getAttribute('data-value');
const x = (window.innerWidth - e.pageX * position) / 90;
const y = (window.innerHeight - e.pageY * position) / 90;
el.style.transform = `translateX(${x}px) translateY(${y}px)`;
});
}