본문 바로가기
UX 개발/UX - 커서 & 클릭 & 키보드

마우스 홀드(클릭 유지) 이벤트로 애니메이션 구현하기

반응형

커스텀 커서와 프리로더를 사용한 홀드 트리거 인터랙션 만들기

이 블로그 포스트에서는 커스텀 커서와 프리로더 버튼을 사용하여 홀드 트리거 인터랙션을 만드는 과정을 안내합니다. 이 인터랙션은 버튼을 눌러 확인하거나 기능을 잠금 해제하는 등의 시나리오에서 유용합니다. HTML, CSS 및 JavaScript를 사용하여 이 효과를 달성할 것입니다.

HTML 구조

먼저 기본 HTML 구조를 설정합니다. 커스텀 커서와 프리로더 섹션이 버튼으로 사용됩니다.

<div class="cursor">
  <div class="cursor__default">
    <span class="cursor__default__inner"></span>
  </div>
  <div class="cursor__trace">
    <span class="cursor__trace__inner"></span>
  </div>
</div>

<section class="preloader shown-area">
  <button class="preloader__btn">
    <span class="preloader__btn_hold"></span>
  </button>
</section>

<section class="header hidden-area">
  Header
</section>

CSS 스타일링

다음으로, 요소들을 스타일링합니다. 커서는 마우스 움직임을 따라가고, 프리로더 버튼은 눌린 상태에서 확대됩니다.

body {
  cursor: none !important;
  background-color: #1e4029;
}

.shown-area {
  display: block;
  opacity: 1;
}

.hidden-area {
  display: none;
  opacity: 0;
}

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9999;
  pointer-events: none !important;
}

.cursor__default__inner {
  position: absolute;
  display: inline-block;
  width: 20px;
  height: 20px;
  background-color: rgba(10, 27, 16, 2.5);
  border: 2px solid #d5a54e;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

.cursor__trace__inner {
  position: absolute;
  display: inline-block;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background-color: rgba(0, 0, 0, .25);
  transform: translate(-50%, -50%);
  transition: all .04s ease;
}

.cursor--active .cursor__trace__inner {
  transform: scale(0.5) translate(-100%, -100%);
  transition: transform .3s ease;
}

@keyframes ripple {
  0% {
    transform: scale(0);
    opacity: 1;
  }
  20% {
    transform: scale(5);
    opacity: 1;
  }
  100% {
    transform: scale(10);
    opacity: 0;
  }
}

.ripple {
  position: absolute;
  display: inline-block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: #d5a54e;
  animation: ripple .5s ease-out;
  animation-fill-mode: forwards;
  z-index: -1;
}

.preloader {
  position: absolute;
  top: 0;
  left: 0;
  background-color: #000000;
  width: 100%;
  height: 100%;
  z-index: 999;
}

.preloader__btn {
  position: absolute;
  top: 50vh;
  left: 50vw;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  border: none;
  color: rgb(213, 165, 78);
  background: url('https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?q=80&w=2886&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D');
  background-size: cover;
  margin-top: -60px;
  margin-left: -60px;
}

.preloader__btn_hold {
  font-size: 19px;
  line-height: 20px;
  font-weight: 800;
  letter-spacing: normal;
}

인터랙션을 위한 JavaScript

마지막으로, 커서 움직임, 버튼 스케일링, 홀드 트리거 인터랙션을 처리하는 JavaScript를 추가합니다.

// Handle cursor movement for both mouse and touch events
function moveCursor(e) {
  const cursorDefaultInner = document.querySelector(".cursor__default__inner");
  const cursorTraceInner = document.querySelector(".cursor__trace__inner");

  const clientX = e.clientX || (e.touches && e.touches[0].clientX);
  const clientY = e.clientY || (e.touches && e.touches[0].clientY);

  cursorDefaultInner.style.top = clientY + "px";
  cursorDefaultInner.style.left = clientX + "px";

  cursorTraceInner.style.top = clientY + "px";
  cursorTraceInner.style.left = clientX + "px";
}

document.addEventListener("mousemove", moveCursor);
document.addEventListener("touchmove", moveCursor);

const cursor = document.querySelector(".cursor");

// Handle cursor active state for both mouse and touch events
function activateCursor() {
  cursor.classList.add("cursor--active");
}

function deactivateCursor() {
  cursor.classList.remove("cursor--active");
}

document.addEventListener("mousedown", activateCursor);
document.addEventListener("touchstart", activateCursor);

document.addEventListener("mouseup", deactivateCursor);
document.addEventListener("touchend", deactivateCursor);
document.addEventListener("touchcancel", deactivateCursor);

// Create ripple effect for both mouse and touch events
function createRipple(e) {
  const clientX = e.clientX || (e.touches && e.touches[0].clientX);
  const clientY = e.clientY || (e.touches && e.touches[0].clientY);

  let ripple = document.createElement("span");

  ripple.classList.add("ripple");

  cursor.appendChild(ripple);

  ripple.style.top = clientY - ripple.clientHeight / 2 + "px";
  ripple.style.left = clientX - ripple.clientWidth / 2 + "px";

  ripple.addEventListener("animationend", () => {
    cursor.removeChild(ripple);
  });
}

document.addEventListener("click", createRipple);
document.addEventListener("touchstart", (e) => {
  createRipple(e);
  // Prevent the default touch event to avoid issues with click handling
  e.preventDefault();
});

const preloaderBtn = document.querySelector(".preloader__btn");

let scale = 1;
let intervalId = null;

const preloaderHideThreshold = 18;
const preloaderHideGap = 0.175;

// Handle preloader button scaling for both mouse and touch events
function startScaling() {
  clearInterval(intervalId);
  intervalId = setInterval(() => {
    scale += preloaderHideGap;
    console.log(scale);
    preloaderBtn.style.transform = `scale(${scale})`;
    if (scale >= preloaderHideThreshold) {
      alert("meow");
      clearInterval(intervalId);
      scale = 1;
    }
  }, 10);
}

function stopScaling() {
  clearInterval(intervalId);
  intervalId = setInterval(() => {
    scale -= preloaderHideGap;
    console.log(scale);
    preloaderBtn.style.transform = `scale(${scale})`;
    if (scale <= 1) {
      clearInterval(intervalId);
      scale = 1;
    }
  }, 10);
}

preloaderBtn.addEventListener("mousedown", startScaling);
preloaderBtn.addEventListener("touchstart", (e) => {
  startScaling();
  // Prevent the default touch event to avoid issues with click handling
  e.preventDefault();
});

preloaderBtn.addEventListener("mouseup", stopScaling);
preloaderBtn.addEventListener("touchend", stopScaling);
preloaderBtn.addEventListener("touchcancel", stopScaling);

설명

  1. 커스텀 커서: 마우스 움직임을 따라가는 커스텀 커서를 생성합니다. 커서는 기본 내부 커서와 추적 커서의 두 부분으로 구성됩니다.
  2. 커서 리플 효과: 클릭 시 커서 위치에서 리플 효과가 생성되어 시각적 피드백을 제공합니다.
  3. 프리로더 버튼: 프리로더 버튼은 눌린 상태에서 점차 확대됩니다. 버튼이 충분히 오래 눌리면(preloaderHideThreshold로 정의됨) 경고 창이 표시됩니다.
  4. 버튼 스케일링: 버튼이 눌린 동안 점차 확대되고, 마우스를 놓으면 다시 축소됩니다.

이 접근 방식은 시각적으로 매력적이고 인터랙티브한 홀드 트리거 버튼을 커스텀 커서 효과와 함께 제공합니다. 스타일과 인터랙션을 쉽게 사용자 맞춤화하여 특정 요구에 맞출 수 있습니다.

반응형
❤️ 외주/과외 문의
🖥️ 클라우드 메뉴판 : 디지털팝