# 27.마우스트레일

In 
ui

# 목차

  • 1. 마우스 트레일 구현
    • 1.1. 기본예제
    • 1.2. 실전예제
  • 2. GSAP을 적용한 마우스 트레일 기능 만들기
    • 2.1. 마우스트레일
    • 2.2. 특정요소에 효과 추가
    • 2.3. 패럴렉스 효과

# 1. 마우스 트레일 구현

# 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>
  1. 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;
}
  1. 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. 실전예제

실행화면

기본세팅

  1. 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>
  1. 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%;
	}
}
  1. 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');
	});
})();

여기까지 실행화면 실행화면

  1. 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>
  1. 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;
}
  1. js 작성
const horizontal = document.querySelector('.horizontal');
const vertical = document.querySelector('.vertical');
const target = document.querySelector('.target');
const tag = document.querySelector('.tag');
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)`;
	});
});

여기까지 실행화면 실행화면

  1. 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>
  1. 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;
}
  1. 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을 적용한 마우스 트레일 기능 만들기

gsap cdn
https://cdnjs.com/libraries/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. 특정요소에 효과 추가

실행화면

  1. body 자식으로 .cursor 추가
<body>
	<div class="cursor"></div>
</body>
  1. 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>
... 생략
  1. 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;
}
  1. 스크립트 작성
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)`;
	});
}