서버의 기술, 웹의 경험, 디자인의 창조력, 코드의 세계

+61 2 8091 3767

질문, 의견 또는 우려 사항이 있으십니까? 당사의 전담 전문가 팀은 귀하의 의견을 듣고 도움을 드릴 준비가 되어 있습니다. 소셜 미디어, 전화, 실시간 채팅을 통해 연락해 주세요.

어떻게 하면 팔로잉 마우스 커서를 적용할수 있나요?🖱️

테크플레이 블로그를 PC에서 보면 독특한 마우스 커서 이펙트 효과가 적용된 것을 볼 수 있습니다. 이러한 커서를 “팔로잉 마우스 커서” 또는 “팔로잉 마우스 포인터” 라고 부르는데, 이런 효과를 적용하기 위해서는 적지 않은 코드들이 들어가게 됩니다. 자신만의 사이트에 여러분이 원하는 커서를 적용하고 싶다면, 예제에서 빠르게 적용해 볼 수 있도록 여러가지를 가져와 봤습니다. 😊(세상엔 똑똑한 분이 참 많으셔요.)


Ghost cursor – CodePenChallenge

귀여운 고스트 커서 입니다. 클릭 할 때마다 눈을 깜빡이네요. 할로윈 사이트에 어울릴 것 같아요. 👻

See the Pen Ghost cursor – CodePenChallenge by Fabio Ottaviani (@supah) on CodePen.

Custom Cursor – Circle Follows The Mouse Pointer

갤러리, 포트폴리오 이미지 링크 호버시 유용할 것 같습니다.

See the Pen Custom Cursor – Circle Follows The Mouse Pointer by Cojea Gabriel (@gabrielcojea) on CodePen.

Stars following mouse

별이 좀 정신없이 튀어나오지만, 조금 조정하여 쓰면 괜찮을 것 같습니다. 이벤트 페이지에 어울릴 것 같아요.

See the Pen Stars following mouse by David Hartley (@davidhartley) on CodePen.

Twinkle Star Following Cursor

귀여운 별들이 반짝거리네요. 어디에 어울릴까요?

See the Pen Twinkle Star Following Cursor by Ziv Rozov (@zivro) on CodePen.

Following Cursor

어디든 잘 어울릴 것 같습니다.

See the Pen Following Cursor by XiChen (@xichen) on CodePen.

Follow cursor mouse with TweenMax.js

강력한 트윈맥스 라이브러리네요, 갤러리등에 잘 어울릴 것 같습니다.

See the Pen Follow cursor mouse with TweenMax.js by pierrinho (@pierrinho) on CodePen.

Curzr | Free cursor library

이 사이트에 적용한 커서가 5번 글리치 커서 이며, 적용하는 방법에 대해 아래 챕터에서 다뤄보겠습니다.

See the Pen Curzr | Free cursor library by Taylon, Chan (@tin-fung-hk) on CodePen.


워드프레스에 팔로잉 마우스 커서 적용

아래 소심하게 캡쳐한 gif ani를 보면 마우스아이콘을 on/off 할 수 있게 적용해 보았습니다. 이유는 모든 사람이 커서효과를 좋아한다고 볼 수도 없으며, 그냥 개인 취향이 담긴 커서 이거니와, 특히 맥 사파리(Safari)에서는 자바스크립트 엔진이 신통치 않아 아주 느립니다. 그래서 맥 사파리에서는 off하는게 좋고, 애플 MAC에서라도 크롬이나 파이어폭스에서는 성능 이슈 없이 사용 할 수 있으니 이점 참고 바랍니다.

Curzr | Free cursor library 5번 커서가 마음에 들어 적용해보겠습니다. 근데 코드를 아는 분들에게는 어렵지 않지만 5번 커서만 적용하기 위해서는 약간의 코드 수정이 필요합니다.

JS 수정

불필요한 코드들은 모두 걷어내고, 마우스 온/오프 기능을 추가 했습니다. 페이지 이동할 때 상태 값을 기억할 수 있도록 로컬 스토리지에 상태 값을 저장할 수 있도록 했습니다.

// GlitchEffect 클래스 정의
class GlitchEffect {
  constructor() {
    // 기본 설정과 변수 초기화
    this.root = document.body  // 페이지의 body 태그를 root로 설정
    this.cursor = document.querySelector(".glitch-effect")  // CSS 클래스가 'glitch-effect'인 요소를 cursor로 설정

    // 커서 이동과 관련된 변수 초기화
    this.distanceX = 0, 
    this.distanceY = 0,
    this.pointerX = 0,
    this.pointerY = 0,
    this.previousPointerX = 0
    this.previousPointerY = 0
    this.cursorSize = 15  // 커서의 기본 크기 설정
    this.glitchColorB = '#00feff'  // 글리치 이펙트의 첫 번째 색상
    this.glitchColorR = '#ff4f71'  // 글리치 이펙트의 두 번째 색상

    // 커서의 스타일 설정
    this.cursorStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      top: `${ this.cursorSize / -1.0 }px`,  // 커서 위치 조정
      left: `${ this.cursorSize / -1.0 }px`,  // 커서 위치 조정
      zIndex: '2147483647',  // z-index를 최대값으로 설정하여 항상 위에 보이도록 함
      width: `${ this.cursorSize }px`,  // 커서의 너비 설정
      height: `${ this.cursorSize }px`,  // 커서의 높이 설정
      backgroundColor: '#222',  // 커서의 기본 배경색
      borderRadius: '50%',  // 커서를 원형으로 만듦
      boxShadow: `0 0 0 ${this.glitchColorB}, 0 0 0 ${this.glitchColorR}`,  // 글리치 이펙트를 위한 그림자 색상
      transition: '100ms, transform 100ms',  // 부드러운 전환 효과
      userSelect: 'none',  // 텍스트 선택 방지
      pointerEvents: 'none'  // 포인터 이벤트 방지
    }

    // backdrop-filter 지원 여부에 따라 스타일을 조정
    if (CSS.supports("backdrop-filter", "invert(1)")) {
      this.cursorStyle.backdropFilter = 'invert(1)'  // invert 필터 적용
      this.cursorStyle.backgroundColor = '#fff0'  // 배경색 투명도 조정
    } else {
      this.cursorStyle.backgroundColor = '#222'  // 지원하지 않을 경우 기본 색상 사용
    }

    // 초기화 함수 호출
    this.init(this.cursor, this.cursorStyle)
  }

  // init 함수: 커서 스타일 적용 및 활성화
  init(el, style) {
    Object.assign(el.style, style)  // 전달받은 요소에 스타일 적용
    setTimeout(() => {
      this.cursor.removeAttribute("hidden")  // 숨김 속성 제거
    }, 500)
    this.cursor.style.opacity = 1  // 커서를 불투명하게 설정
  }

  // move 함수: 마우스 이동에 따라 커서 위치 및 스타일 조정
  move(event) {
    // 이전 포인터 위치를 현재 위치로 업데이트
    this.previousPointerX = this.pointerX
    this.previousPointerY = this.pointerY
    // 새로운 포인터 위치 계산
    this.pointerX = event.pageX + this.root.getBoundingClientRect().x
    this.pointerY = event.pageY + this.root.getBoundingClientRect().y
    // 포인터 이동 거리를 계산하고 제한을 두어 글리치 효과 조절
    this.distanceX = Math.min(Math.max(this.previousPointerX - this.pointerX, -10), 10)
    this.distanceY = Math.min(Math.max(this.previousPointerY - this.pointerY, -10), 10)

    // 특정 요소 위에 있을 때 hover 함수 호출
    if (event.target.localName === 'svg' || 
        event.target.localName === 'a' || 
        event.target.onclick !== null ||
        Array.from(event.target.classList).includes('curzr-hover')) {
      this.hover()
    } else {
      this.hoverout()
    }

    // 커서의 위치와 그림자(글리치 효과)를 업데이트
    this.cursor.style.transform = `translate3d(${this.pointerX}px, ${this.pointerY}px, 0)`
    this.cursor.style.boxShadow = `
      ${+this.distanceX}px ${+this.distanceY}px 0 ${this.glitchColorB}, 
      ${-this.distanceX}px ${-this.distanceY}px 0 ${this.glitchColorR}`
    this.stop()  // 글리치 효과 중지
  }

  // hover 함수: 커서 크기 변경
  hover() {
    this.cursorSize = 30  // 크기 증가
  }

  // hoverout 함수: 커서 크기 복원
  hoverout() {
    this.cursorSize = 15  // 기본 크기로 복원
  }

  // click 함수: 클릭 시 커서 스타일 변화
  click() {
    this.cursor.style.transform += ` scale(0.75)`  // 클릭 시 축소 효과
    setTimeout(() => {
      this.cursor.style.transform = this.cursor.style.transform.replace(` scale(0.75)`, '')  // 원래 크기로 복원
    }, 35)
  }

  // stop 함수: 글리치 이펙트 중지
  stop() {
    if (!this.moving) {
      this.moving = true
      setTimeout(() => {
        this.cursor.style.boxShadow = ''  // 그림자(글리치 효과) 제거
        this.moving = false
      }, 50)
    }
  }

  // hidden 함수: 커서 숨기기
  hidden() {
    this.cursor.style.opacity = 0  // 불투명도를 0으로 설정하여 숨김
    setTimeout(() => {
      this.cursor.setAttribute("hidden", "hidden")  // 숨김 속성 설정
    }, 500)
  }
}

// GlitchEffect 인스턴스 생성 및 이벤트 핸들러 함수 정의
let cursor = new GlitchEffect();

function handleMouseMove(event) {
  cursor.move(event);  // 마우스 이동 이벤트 처리
}
function handleTouchMove(event) {
  cursor.move(event.touches[0]);  // 터치 이동 이벤트 처리
}
function handleClick() {
  if (typeof cursor.click === 'function') {
    cursor.click();  // 클릭 이벤트 처리
  }
}

// updateCursorEffect 함수: 마우스 이펙트 상태에 따라 커서 업데이트
function updateCursorEffect() {
  const isChecked = localStorage.getItem('mouseEffect') === 'true';
  document.getElementById('tb-bell').checked = isChecked;

  if (isChecked) {
    document.body.style.cursor = 'none';  // 기본 커서 숨김
    cursor = new GlitchEffect();
    document.onmousemove = handleMouseMove;
    document.ontouchmove = handleTouchMove;
    document.onclick = handleClick;
  } else {
    document.body.style.cursor = 'auto';  // 기본 커서 복원
    cursor.hidden();
  }
}

// tb-bell 요소의 상태 변경에 따라 updateCursorEffect 함수 호출
document.getElementById('tb-bell').addEventListener('change', function() {
  localStorage.setItem('mouseEffect', this.checked);
  updateCursorEffect();
});

// 페이지 로드 시 updateCursorEffect 함수를 호출하여 초기 상태 설정
window.onload = updateCursorEffect;

위 자바스크립트를 차일드 테마에 JS폴더를 생성하고, 원하는 파일명으로 저장합니다. 전 assets/js폴더를 만들어서 mouse.js 로 저장했습니다.

HTML 마우스 효과 및 온/오프 버튼 적용

“아이콘 라이브러리”는 헤더에 링크를 추가하고, “글리치 마우스 커서”는 원하는 곳에 삽입합니다. “온/오프 토글 버튼”도 마찬가지로 원하는 곳에 넣어주면 됩니다.

<!-- 아이콘 라이브러리 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.3.67/css/materialdesignicons.min.css" />

<!-- 마우스 효과 -->
<div class="glitch-effect"></div>

<!-- 마우스 버튼 on/off --> 
  <label class="toggle-button" for="tb-bell" style="--toggle-button-checked-color:#2979FF">
  <input class="toggle-button__input curzr-hover" id="tb-bell" aria-labelledby="tb-bell-tip" type="checkbox">
  <i class="toggle-button__checked mdi mdi-mouse curzr-hover" role="presentation"></i>
  <i class="toggle-button__unchecked mdi mdi-mouse-off curzr-hover" role="presentation"></i>
  <span class="toggle-button__background curzr-hover" role="presenatation"></span>
  <span class="toggle-button__tip" id="tb-heart-tip">mouse effect</span>
</label>

CSS 스타일 입히기

차일드 테마에 /assets/css/mouse.css 파일을 만들어서 아래 스타일을 적용합니다.

  /*toggle*/
  .toggle-button {
    --toggle-button-checked-color: #e2e2e3;
    --toggle-button-unchecked-color: rgba(0, 0, 0, 0.56);
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 40px;
    height: 40px;
    color: var(--toggle-button-unchecked-color);
    font-family: inherit;
    font-size: inherit;
  }
  .toggle-button::before {
    position: absolute;
    box-sizing: inherit;
    width: 100%;
    height: 100%;
    content: "";
    background-color: currentColor;
    border-radius: 50%;
    opacity: 0;
    transform-origin: 50% 50%;
    transform: scale(0.25);
    transition: opacity 120ms ease-out, transform 120ms ease-out;
  }
  .toggle-button:focus::before, .toggle-button:focus-within::before {
    opacity: 0.08;
    transform: scale(0.8);
  }
  .toggle-button:hover::before {
    opacity: 0.04;
    transform: scale(0.9);
  }
  .toggle-button:active::before {
    opacity: 0.12;
    transform: scale(1);
  }
  .toggle-button__input {
    position: absolute;
    z-index: 1;
    width: 100%;
    height: 100%;
    box-sizing: inherit;
    opacity: 0;
    cursor: pointer;
  }
  .toggle-button__checked, .toggle-button__unchecked {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: inherit;
    width: 24px;
    height: 24px;
    font-size: 24px;
    pointer-events: none;
    transform-origin: 50% 50%;
    transition: opacity 120ms ease-out, transform 120ms ease-out;
  }
  .toggle-button__checked {
    color: var(--toggle-button-checked-color);
  }
  .toggle-button__input:not(:checked) ~ .toggle-button__checked {
    opacity: 0;
    transform: scale(0.375);
  }
  .toggle-button__input:checked ~ .toggle-button__checked {
    opacity: 1;
    transform: scale(1);
    transition-delay: 180ms;
    transition-duration: 180ms;
  }
  .toggle-button__unchecked {
    color: currentColor;
  }
  .toggle-button__input:not(:checked) ~ .toggle-button__unchecked {
    opacity: 1;
    transition-delay: 120ms;
  }
  .toggle-button__input:checked ~ .toggle-button__unchecked {
    opacity: 0;
  }
  .toggle-button__background {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: inherit;
    width: 100%;
    height: 100%;
    pointer-events: none;
  }
  .toggle-button__background::before, .toggle-button__background::after {
    position: absolute;
    box-sizing: inherit;
    content: "";
    transform-origin: 50% 50%;
  }
  .toggle-button__background::before {
    width: 100%;
    height: 100%;
    background-color: var(--toggle-button-checked-color);
    border-radius: 50%;
    opacity: 0;
    transform: scale(0.6);
  }
  .toggle-button__background::after {
    top: 50%;
    left: 50%;
    width: 0.25rem;
    height: 0.25rem;
    margin-top: -0.125rem;
    margin-left: -0.125rem;
    border-radius: 50%;
    opacity: 0;
    transform: scale(0.5);
    box-shadow: 0px -24px 0px 0rem #f1cf01, 18.7639555792px -14.9637552446px 0px 0rem #46f101, 23.3982698924px 5.340502415px 0px 0rem #01f18a, 10.4132097388px 21.6232528297px 0px 0rem #018af1, -10.4132097388px 21.6232528297px 0px 0rem #4601f1, -23.398269892px 5.340502415px 0px 0rem #f101cf, -18.7639554503px -14.9637552232px 0px 0rem #f10101;
  }
  .toggle-button__input:checked ~ .toggle-button__background::before {
    -webkit-animation: toggle-button-bg-animation-before 390ms ease-out forwards;
            animation: toggle-button-bg-animation-before 390ms ease-out forwards;
  }
  .toggle-button__input:checked ~ .toggle-button__background::after {
    -webkit-animation: toggle-button-bg-animation-after 360ms ease-out 210ms forwards;
            animation: toggle-button-bg-animation-after 360ms ease-out 210ms forwards;
  }
  .toggle-button__tip {
    position: absolute;
    bottom: 100%;
    box-sizing: inherit;
    max-width: 144px;
    padding: 0px 6px;
    color: white;
    font-size: 0.625rem;
    letter-spacing: 0.0275em;
    line-height: 20px;
    text-shadow: 0px 0px 1px rgba(0, 0, 0, 0.08);
    background-color: rgba(0, 0, 0, 0.56);
    border-radius: 3px;
    overflow: hidden;
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transform-origin: center bottom;
    transform: translateY(-4px);
    transition: opacity 60ms ease-out 0ms, transform 0ms ease-out 60ms;
  }
  .toggle-button__input:focus-visible ~ .toggle-button__tip, .toggle-button__input:hover ~ .toggle-button__tip {
    opacity: 1;
    transform: translateY(-8px);
    transition-delay: 90ms;
    transition-duration: 120ms;
  }
  
  @-webkit-keyframes toggle-button-bg-animation-before {
    0% {
      opacity: 0;
      transform: scale(0.3);
    }
    2.5% {
      opacity: 0.87;
    }
    60% {
      opacity: 0.2;
      transform: scale(0.75);
      -webkit-animation-timing-function: linear;
              animation-timing-function: linear;
    }
    100% {
      opacity: 0;
      transform: scale(1);
    }
  }
  
  @keyframes toggle-button-bg-animation-before {
    0% {
      opacity: 0;
      transform: scale(0.3);
    }
    2.5% {
      opacity: 0.87;
    }
    60% {
      opacity: 0.2;
      transform: scale(0.75);
      -webkit-animation-timing-function: linear;
              animation-timing-function: linear;
    }
    100% {
      opacity: 0;
      transform: scale(1);
    }
  }
  @-webkit-keyframes toggle-button-bg-animation-after {
    0% {
      opacity: 0;
      transform: scale(0.5);
    }
    5% {
      opacity: 1;
      transform: scale(0.5);
    }
    60% {
      transform: scale(0.875);
      -webkit-animation-timing-function: ease-in;
              animation-timing-function: ease-in;
    }
    100% {
      opacity: 0;
      transform: scale(1);
    }
  }
  @keyframes toggle-button-bg-animation-after {
    0% {
      opacity: 0;
      transform: scale(0.5);
    }
    5% {
      opacity: 1;
      transform: scale(0.5);
    }
    60% {
      transform: scale(0.875);
      -webkit-animation-timing-function: ease-in;
              animation-timing-function: ease-in;
    }
    100% {
      opacity: 0;
      transform: scale(1);
    }
  }
  
  .toggle-button {
    margin: 8px;
  }

워드프레스 함수 파일 수정

아래 코드에 주석을 자세히 달아 두었습니다. 차일드 테마 함수 파일(child-theme/functions.php)에 위에서 만든 mouse.css와 mouse.js파일을 불러 옵니다.

// mouse effect 스타일시트 추가를 위한 액션 후크
add_action('wp_enqueue_scripts', 'custom_style_sheet');
function custom_style_sheet() {
    wp_enqueue_style( 'custom-styling', get_stylesheet_directory_uri() . '/assets/css/mouse.css' );
    // 여기서 wp_enqueue_style 함수는 WordPress 사이트에 사용자 정의 스타일시트를 추가합니다.
    // 'custom-styling'은 스타일시트의 핸들 이름입니다.
    // get_stylesheet_directory_uri() . '/assets/css/mouse.css'는 스타일시트의 경로를 지정합니다.
}

// mouse effect 스크립트 추가를 위한 액션 후크
add_action( 'wp_enqueue_scripts', 'Scripts_mouse_effect' );
function Scripts_mouse_effect() {
    wp_enqueue_script('mouse-e-script', get_stylesheet_directory_uri() . '/assets/js/mouse.js', array(), '1.0.0', true );
    // 여기서 wp_enqueue_script 함수는 WordPress 사이트에 사용자 정의 자바스크립트 파일을 추가합니다.
    // 'mouse-e-script'는 스크립트의 핸들 이름입니다.
    // get_stylesheet_directory_uri() . '/assets/js/mouse.js'는 스크립트 파일의 경로를 지정합니다.
    // array()는 스크립트의 종속성을 나타내며, 여기서는 종속성이 없음을 의미합니다.
    // '1.0.0'은 스크립트 버전을 지정합니다.
    // true는 스크립트를 페이지의 footer에 로드하도록 지정합니다.
}

// 스크립트 태그에 defer 속성 추가를 위한 필터 후크
add_filter('script_loader_tag', 'add_defer_attribute', 10, 2);
function add_defer_attribute($tag, $handle) {
    if ('mouse-e-script' !== $handle)
        return $tag;
    // 'mouse-e-script' 핸들을 가진 스크립트에만 defer 속성을 추가합니다.
    return str_replace(' src', ' defer="defer" src', $tag);
    // defer 속성을 사용하면 브라우저가 HTML 파싱을 방해하지 않고 스크립트를 비동기적으로 로드합니다.
}

마무리

한 가지 예제를 따라 해봤습니다. 다른 여러 사람들이 만든 커서 효과 역시도 이와 같은 방식으로 적용이 가능합니다. 자신의 사이트에 적용하려면 플랫폼에 따라, 코드 수정방식이나 요구 사항이 다를 수는 있습니다.

위 예제가 잘 적용되지 않나요? js, css, 파일이 잘 불러오는지 확인해 보세요. 브라우저 개발자 콘솔을 통해 에러 로그를 확인해 보세요. 도움이 필요하면 언제든지 연락주세요.


Share this article
Shareable URL
Prev Post

SSH 보안 키를 생성하여 서버에 접속하는 방법

Next Post

스크린(Screen)터미널 멀티 플렉스 활용으로 작업 효율 극대화 하기 – 우분투 22.04

댓글을 남겨주세요.😊

이메일 주소는 공개되지 않습니다. * 표시는 필수 입력 항목입니다.