TikTok Tutorial #62- How to create an Useful Cursor Library

Check out this useful cursor library and use it in your web project!

If you found us on TikTok on the following post, check out this article and copy-paste the full code!

Happy coding! 😻

@creative.tim Pick your favourite cursor and enjoy its moves. 🤩 #cursor #webdev #coding ♬ original sound - Creative Tim

Contents:
1. HTML Code
2. CSS Code
3. Javascript Code

Get your code ⬇️

1. HTML Code

<header>
  <span>
    <a href="https://profyr.com/" target="_blank" class="shift-in curzr-hover"><svg xmlns="http://www.w3.org/2000/svg" id="Layer_2" viewBox="0 0 139.12 36.47">
        <g id="Layer_1-2">
          <g>
            <path class="cls-1" d="M0,18.23C0,7.92,7.66,0,18.23,0c6.36,0,11.97,3.16,15.03,8.12l-6.91,4.01c-1.55-2.71-4.56-4.31-8.12-4.31-6.21,0-10.22,4.16-10.22,10.42s4.01,10.42,10.22,10.42c3.56,0,6.61-1.6,8.12-4.31l6.91,4.01c-3.01,4.96-8.62,8.12-15.03,8.12C7.66,36.47,0,28.55,0,18.23Z" />
            <path class="cls-1" d="M62.22,10.72v25.05h-7.51v-2.35c-1.35,1.85-3.86,3.06-7.16,3.06-5.06,0-9.37-3.61-9.37-10.37V10.72h7.51v14.28c0,3.11,1.95,4.56,4.36,4.56,2.75,0,4.66-1.6,4.66-5.16V10.72h7.51Z" />
            <path class="cls-1" d="M84.01,10.22v8.52c-3.11-.5-7.51,.75-7.51,5.71v11.32h-7.51V10.72h7.51v4.46c1-3.36,4.36-4.96,7.51-4.96Z" />
            <path class="cls-1" d="M108.06,28.75v7.01h-20.04v-5.01l9.47-13.03h-8.97v-7.01h19.04v5.01l-9.47,13.03h9.97Z" />
            <path class="cls-1" d="M128.85,10.22v8.52c-3.11-.5-7.51,.75-7.51,5.71v11.32h-7.51V10.72h7.51v4.46c1-3.36,4.36-4.96,7.51-4.96Z" />
            <path class="cls-1" d="M129.6,31.71c0-2.6,2.15-4.76,4.76-4.76s4.76,2.15,4.76,4.76-2.15,4.76-4.76,4.76-4.76-2.15-4.76-4.76Z" />
          </g>
        </g>
      </svg></a>
  </span>
  <span>
    <a href="https://github.com/TaylonChan/Curzr" target="_blank" class="shift-in">Github</a>
  </span>
</header>

<section id="cursor-1" class="container container-cursor-1"></section>
<section id="cursor-2" class="container container-cursor-2"></section>
<section id="cursor-3" class="container container-cursor-3"></section>
<section id="cursor-4" class="container container-cursor-4"></section>
<section id="cursor-5" class="container container-cursor-5"></section>
<section id="cursor-6" class="container container-cursor-6"></section>

<footer>
  <span>
    <a href="https://profyr.com/" target="_blank" class="shift-in">Get them for free</a>
  </span>
  <span class="curzr-hover btn btn-previous">
    <span>
      <small class="curzr-hover shift-in" data-text="Previous">Previous</small>
    </span>
    <br>
    <span>
      <small class="curzr-hover shift-in" data-text="Cursor">Cursor</small>
    </span>
  </span>
  <span></span>
  <span class="curzr-hover btn btn-next">
    <span>
      <small class="curzr-hover shift-in" data-text="Next">Next</small>
    </span>
    <br>
    <span>
      <small class="curzr-hover shift-in" data-text="Cursor">Cursor</small>
    </span>
  </span>
</footer>

<div class="curzr-arrow-pointer" hidden>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    <path class="inner" d="M25,30a5.82,5.82,0,0,1-1.09-.17l-.2-.07-7.36-3.48a.72.72,0,0,0-.35-.08.78.78,0,0,0-.33.07L8.24,29.54a.66.66,0,0,1-.2.06,5.17,5.17,0,0,1-1,.15,3.6,3.6,0,0,1-3.29-5L12.68,4.2a3.59,3.59,0,0,1,6.58,0l9,20.74A3.6,3.6,0,0,1,25,30Z" fill="#F2F5F8" />
    <path class="outer" d="M16,3A2.59,2.59,0,0,1,18.34,4.6l9,20.74A2.59,2.59,0,0,1,25,29a5.42,5.42,0,0,1-.86-.15l-7.37-3.48a1.84,1.84,0,0,0-.77-.17,1.69,1.69,0,0,0-.73.16l-7.4,3.31a5.89,5.89,0,0,1-.79.12,2.59,2.59,0,0,1-2.37-3.62L13.6,4.6A2.58,2.58,0,0,1,16,3m0-2h0A4.58,4.58,0,0,0,11.76,3.8L2.84,24.33A4.58,4.58,0,0,0,7,30.75a6.08,6.08,0,0,0,1.21-.17,1.87,1.87,0,0,0,.4-.13L16,27.18l7.29,3.44a1.64,1.64,0,0,0,.39.14A6.37,6.37,0,0,0,25,31a4.59,4.59,0,0,0,4.21-6.41l-9-20.75A4.62,4.62,0,0,0,16,1Z" fill="#111920" />
  </svg>
</div>

<div class="curzr-big-circle" hidden>
  <div class="circle"></div>
  <div class="dot"></div>
</div>

<div class="curzr-ring-dot" hidden>
  <div class="curzr-dot"></div>
</div>

<div class="curzr-circle-and-dot" hidden></div>

<div class="curzr-glitch-effect" hidden></div>

<svg class="curzr-motion" hidden>
  <filter id="motionblur" x="-100%" y="-100%" width="400%" height="400%">
    <feGaussianBlur class="curzr-motion-blur" stdDeviation="0, 0" />
  </filter>
  <circle cx="50%" cy="50%" r="5" fill="#292927" filter="url(#motionblur)" />
</svg>

<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js"></script>
<script>
  WebFont.load({
    google: {
      families: ['Plus Jakarta Sans']
    }
  });
</script>

2. CSS Code

* {
  box-sizing: border-box;
  cursor: none;
}

:root {
  --cursor-1-bg: #ffffff;
  --cursor-2-bg: #151a1f;
  --cursor-3-bg: #f1f68f;
  --cursor-4-bg: #b0efda;
  --cursor-5-bg: #d5c9fb;
  --cursor-6-bg: #ffbc90;

  --cursor-1-bg-line: #eef1f4;
  --cursor-2-bg-line: #1f272e;
  --cursor-3-bg-line: #f1f68f;
  --cursor-4-bg-line: #b0efda;
  --cursor-5-bg-line: #d5c9fb;
  --cursor-6-bg-line: #ffbc90;

  --curzr-logo-color: #292927;

  body {
    position: relative;
    width: 100%;
    font-family: 'Plus Jakarta Sans', Arial, Helvetica, sans-serif;
    font-weight: bold;
    font-size: .875rem;
    overflow: hidden;
    color: #292927;

    header {
      position: fixed;
      display: flex;
      justify-content: space-between;
      width: 100%;
      z-index: 7;
      padding: 2rem;

      span {
        overflow: hidden;
        transition: 250ms;
        user-select: none;

        &:hover {
          opacity: .75;
        }

        & > * {
          display: inline-block;
        }
      }

      svg {
        width: 75px;

        .cls-1{
          fill: var(--curzr-logo-color);
        }
      }
    }

    .container {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100vh;
      background-size: 40px 80px;
      background-position: center;
      background-attachment: fixed;
    }

    .container-cursor-1 {
      z-index: 6;
      background-color: var(--cursor-1-bg);
      background-image:  
        linear-gradient(var(--cursor-1-bg-line) 1px, transparent 1px), 
        linear-gradient(to right, var(--cursor-1-bg-line) 1px, var(--cursor-1-bg) 1px);
    }

    .container-cursor-2 {
      z-index: 5;
      background-color: var(--cursor-2-bg);
      background-image:  
        linear-gradient(var(--cursor-2-bg-line) 1px, transparent 1px), 
        linear-gradient(to right, var(--cursor-2-bg-line) 1px, var(--cursor-2-bg) 1px);
    }

    .container-cursor-3 {
      z-index: 4;
      background-color: var(--cursor-3-bg);
      background-image:  
        linear-gradient(var(--cursor-3-bg-line) 1px, transparent 1px), 
        linear-gradient(to right, var(--cursor-3-bg-line) 1px, var(--cursor-3-bg) 1px);
    }

    .container-cursor-4 {
      z-index: 3;
      background-color: var(--cursor-4-bg);
      background-image:  
        linear-gradient(var(--cursor-4-bg-line) 1px, transparent 1px), 
        linear-gradient(to right, var(--cursor-4-bg-line) 1px, var(--cursor-4-bg) 1px);
    }

    .container-cursor-5 {
      z-index: 2;
      background-color: var(--cursor-5-bg);
      background-image:  
        linear-gradient(var(--cursor-5-bg-line) 1px, transparent 1px), 
        linear-gradient(to right, var(--cursor-5-bg-line) 1px, var(--cursor-5-bg) 1px);
    }

    .container-cursor-6 {
      z-index: 1;
      background-color: var(--cursor-6-bg);
      background-image:  
        linear-gradient(var(--cursor-6-bg-line) 1px, transparent 1px), 
        linear-gradient(to right, var(--cursor-6-bg-line) 1px, var(--cursor-6-bg) 1px);
    }

    footer {
      position: fixed;
      display: flex;
      bottom: 0;
      justify-content: space-between;
      width: 100%;
      z-index: 7;
      padding: 1.5rem 2rem;
      line-height: 1.25;

      span {
        overflow: hidden;
        user-select: none;

        & > * {
          display: inline-block;
        }
      }
    }

    a {
      color: #292927;
      text-decoration: none;
      user-select: none;
      cursor: none;
    }

    small.shift-in {
      position: relative;
      
      &::after {
        content: attr(data-text);
        position: absolute;
        bottom: -100%;
        left: 0;
      }
    }
  }
}

3. Javascript Code

let sectionName = "cursor-1"
let sectionPrev = ""
let sectionList = [
  "cursor-1",
  "cursor-2",
  "cursor-3",
  "cursor-4",
  "cursor-5",
  "cursor-6"
]
let cursorList = [
  "arrow-pointer",
  "big-circle",
  "ring-dot",
  "circle-and-dot",
  "glitch-effect",
  "motion-blur"
]
let isShiftDone = false

const btnPrevious = document.querySelector(".btn-previous")
const btnNext = document.querySelector(".btn-next")
const header = document.querySelector("header")
const footer = document.querySelector("footer")
const root = document.querySelector(':root')

window.onload = function() {
  shiftIn()
}

btnPrevious.addEventListener('click', function() {
  if (isShiftDone) {
    location.href = "#" + sectionList[(sectionList.indexOf(sectionName) + sectionList.length - 1) % sectionList.length]
  }
})

btnNext.addEventListener('click', function() {
  if (isShiftDone) {
    location.href = "#" + sectionList[(sectionList.indexOf(sectionName) + 1) % sectionList.length]
  }
})

btnPrevious.addEventListener('mouseenter', function() {
  shiftUp('.btn-previous small.shift-in')
})

btnNext.addEventListener('mouseenter', function() {
  shiftUp('.btn-next small.shift-in')
})

let shiftup = setInterval(() => {
  shiftUp('.btn-next small.shift-in')
}, 3000)

window.addEventListener('popstate', function () {
  sectionPrev = sectionName
  sectionName = getAnchor()
  this.clearInterval(shiftup)
  pageChange(sectionName, sectionPrev)
})

function shiftIn() {
  anime({
    targets: '.shift-in',
    translateY: ['50px', '0'],
    delay: anime.stagger(100),
    easing: 'easeInOutSine',
    complete: function() {
      isShiftDone = true
    }
  })

  if (sectionName !== "cursor-2") {
    anime({
      targets: '.shift-in',
      color: '#292927',
      delay: anime.stagger(100)
    })
    root.style.setProperty('--curzr-logo-color', '#292927')
  } else {
    anime({
      targets: '.shift-in',
      color: '#e6e6e6',
      delay: anime.stagger(100)
    })
    root.style.setProperty('--curzr-logo-color', '#e6e6e6')
  }
}

function shiftUp(el) {
  anime({
    targets: el,
    translateY: ['0%', '-100%'],
    duration: 500,
    delay: anime.stagger(100),
    easing: 'easeInOutCubic',
    complete: function() {
    }
  })
}

function getAnchor() {
  let anchor = document.URL.split('#')[1]
  return anchor ? anchor : null
}

function pageChange(sectionName, sectionPrev) {
  isShiftDone = false
  let duration = 1000
  let sectionIndex = sectionList.findIndex((section) => section === sectionName)
  changeCursor(sectionIndex) 
    
  document.getElementById(sectionName).style.zIndex = sectionList.length + 2
  document.getElementById(sectionPrev).style.zIndex = sectionList.length

  anime({
    targets: document.getElementById(sectionName),
    translateX: ['-100%', '0%'],
    easing: 'easeInOutCirc'
  })

  anime({
    targets: document.getElementById(sectionPrev),
    translateX: ['0%', '100%'],
    duration: duration,
    easing: 'easeInOutCirc',
    complete: function() {
      document.getElementById(sectionPrev).style.transform = 'translateX(0%)'
      document.getElementById(sectionPrev).style.zIndex = sectionList.length - sectionList.indexOf(sectionPrev)
      shiftup = setInterval(() => {
        shiftUp('.btn-next small.shift-in')
      }, 3000)
    }
  })

  anime({
    targets: [header, footer],
    translateX: ['0%', '50%'],
    duration: duration,
    easing: 'easeInCirc',
    complete: function() {
      document.getElementById(sectionName).style.zIndex = sectionList.length
      header.style.transform = 'translateX(0%)'
      footer.style.transform = 'translateX(0%)'
      shiftIn()
    }
  })
}

function changeCursor(index) {
  cursor.hidden()
  switch (cursorList[index]) {
    case 'arrow-pointer':
      cursor = new ArrowPointer()
      break
    case 'big-circle':
      cursor = new BigCircle()
      break
    case 'ring-dot':
      cursor = new RingDot()
      break
    case 'circle-and-dot':
      cursor = new CircleAndDot()
      break
    case 'glitch-effect':
      cursor = new GlitchEffect()
      break
    case 'motion-blur':
      cursor = new MotionBlur()
      break
  }
}

class ArrowPointer {
  constructor() {
    this.root = document.body
    this.cursor = document.querySelector(".curzr-arrow-pointer")

    this.position = {
      distanceX: 0, 
      distanceY: 0,
      distance: 0,
      pointerX: 0,
      pointerY: 0,
    },
    this.previousPointerX = 0
    this.previousPointerY = 0
    this.angle = 0
    this.previousAngle = 0
    this.angleDisplace = 0
    this.degrees = 57.296
    this.cursorSize = 20

    this.cursorStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      top: '50%',
      left: '50%',
      transform: 'translate(-50%, -50%)',
      zIndex: '2147483647',
      width: `${ this.cursorSize }px`,
      height: `${ this.cursorSize }px`,
      transition: '250ms, transform 100ms',
      userSelect: 'none',
      pointerEvents: 'none'
    }

    this.init(this.cursor, this.cursorStyle)
  }

  init(el, style) {
    Object.assign(el.style, style)
    setTimeout(() => {
      this.cursor.removeAttribute("hidden")
    }, 500)
    this.cursor.style.opacity = 1
  }

  move(event) {
    this.previousPointerX = this.position.pointerX
    this.previousPointerY = this.position.pointerY
    this.position.pointerX = event.pageX + this.root.getBoundingClientRect().x
    this.position.pointerY = event.pageY + this.root.getBoundingClientRect().y
    this.position.distanceX = this.previousPointerX - this.position.pointerX
    this.position.distanceY = this.previousPointerY - this.position.pointerY
    this.distance = Math.sqrt(this.position.distanceY ** 2 + this.position.distanceX ** 2)
  
    this.cursor.style.transform = `translate3d(${this.position.pointerX}px, ${this.position.pointerY}px, 0)`

    if (this.distance > 1) {
      this.rotate(this.position)
    } else {
      this.cursor.style.transform += ` rotate(${this.angleDisplace}deg)`
    }
  }

  rotate(position) {
    let unsortedAngle = Math.atan(Math.abs(position.distanceY) / Math.abs(position.distanceX)) * this.degrees
    let modAngle
    const style = this.cursor.style
    this.previousAngle = this.angle

    if (position.distanceX <= 0 && position.distanceY >= 0) {
      this.angle = 90 - unsortedAngle + 0
    } else if (position.distanceX < 0 && position.distanceY < 0) {
      this.angle = unsortedAngle + 90
    } else if (position.distanceX >= 0 && position.distanceY <= 0) {
      this.angle = 90 - unsortedAngle + 180
    } else if (position.distanceX > 0 && position.distanceY > 0) {
      this.angle = unsortedAngle + 270
    }

    if (isNaN(this.angle)) {
      this.angle = this.previousAngle
    } else {
      if (this.angle - this.previousAngle <= -270) {
        this.angleDisplace += 360 + this.angle - this.previousAngle
      } else if (this.angle - this.previousAngle >= 270) {
        this.angleDisplace += this.angle - this.previousAngle - 360
      } else {
        this.angleDisplace += this.angle - this.previousAngle
      }
    }
    style.left = `${ -this.cursorSize / 2 }px`
    style.top = `${ 0 }px`
    style.transform += ` rotate(${this.angleDisplace}deg)`
  }

  hidden() {
    this.cursor.style.opacity = 0
    setTimeout(() => {
      this.cursor.setAttribute("hidden", "hidden")
    }, 500)
  }
}

class BigCircle {
  constructor() {
    this.root = document.body
    this.cursor = document.querySelector(".curzr-big-circle")
    this.circle = document.querySelector(".curzr-big-circle .circle")
    this.dot = document.querySelector(".curzr-big-circle .dot")

    this.pointerX = 0
    this.pointerY = 0
    this.cursorSize = 50

    this.circleStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      top: `${ this.cursorSize / -2 }px`,
      left: `${ this.cursorSize / -2 }px`,
      zIndex: '2147483647',
      width: `${ this.cursorSize }px`,
      height: `${ this.cursorSize }px`,
      backgroundColor: '#fff0',
      borderRadius: '50%',
      transition: '500ms, transform 100ms',
      userSelect: 'none',
      pointerEvents: 'none'
    }

    this.dotStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      zIndex: '2147483647',
      width: '6px',
      height: '6px',
      backgroundColor: '#0000',
      borderRadius: '50%',
      userSelect: 'none',
      pointerEvents: 'none',
      transition: '250ms, transform 75ms'
    }

    if (CSS.supports("backdrop-filter", "invert(1) grayscale(1)")) {
      this.circleStyle.backdropFilter = 'invert(0.85) grayscale(1)'
      this.dotStyle.backdropFilter = 'invert(1)'
      this.circleStyle.backgroundColor = '#fff0'
    } else {
      this.circleStyle.backgroundColor = '#000'
      this.circleStyle.opacity = '0.5'
    }

    this.init(this.circle, this.circleStyle)
    this.init(this.dot, this.dotStyle)
  }

  init(el, style) {
    Object.assign(el.style, style)
    setTimeout(() => {
      this.cursor.removeAttribute("hidden")
    }, 500)
    this.cursor.style.opacity = 1
  }

  move(event) {
    this.pointerX = event.pageX
    this.pointerY = event.pageY + this.root.getBoundingClientRect().y
  
    this.circle.style.transform = `translate3d(${this.pointerX}px, ${this.pointerY}px, 0)`
    this.dot.style.transform = `translate3d(calc(-50% + ${this.pointerX}px), calc(-50% + ${this.pointerY}px), 0)`

    if (event.target.localName === 'svg' || 
        event.target.localName === 'a' || 
        event.target.onclick !== null ||
        Array.from(event.target.classList).includes('curzr-hover')) {
      this.hover()
    }
  }

  hover() {
    this.circle.style.transform += ` scale(2.5)`
  }

  click() {
    this.circle.style.transform += ` scale(0.75)`
    setTimeout(() => {
      this.circle.style.transform = this.circle.style.transform.replace(` scale(0.75)`, '')
    }, 35)
  }

  hidden() {
    this.cursor.style.opacity = 0
    setTimeout(() => {
      this.cursor.setAttribute("hidden", "hidden")
    }, 500)
  }
}

class RingDot {
  constructor() {
    this.root = document.body
    this.cursor = document.querySelector(".curzr-ring-dot")
    this.dot = document.querySelector(".curzr-ring-dot .curzr-dot")

    this.pointerX = 0
    this.pointerY = 0
    this.cursorSize = 20

    this.cursorStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      display: 'flex',
      top: `${ this.cursorSize / -2 }px`,
      left: `${ this.cursorSize / -2 }px`,
      zIndex: '2147483647',
      justifyContent: 'center',
      alignItems: 'center',
      width: `${ this.cursorSize }px`,
      height: `${ this.cursorSize }px`,
      backgroundColor: '#fff0',
      boxShadow: '0 0 0 1.25px #292927, 0 0 0 2.25px #edf370',
      borderRadius: '50%',
      transition: '200ms, transform 100ms',
      userSelect: 'none',
      pointerEvents: 'none'
    }

    this.dotStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      zIndex: '2147483647',
      width: '4px',
      height: '4px',
      backgroundColor: '#292927',
      boxShadow: '0 0 0 1px #edf370',
      borderRadius: '50%',
      userSelect: 'none',
      pointerEvents: 'none',
    }

    this.init(this.cursor, this.cursorStyle)
    this.init(this.dot, this.dotStyle)
  }

  init(el, style) {
    Object.assign(el.style, style)
    setTimeout(() => {
      this.cursor.removeAttribute("hidden")
    }, 500)
    this.cursor.style.opacity = 1
  }

  move(event) {
    if (event.target.localName === 'svg' || 
        event.target.localName === 'a' || 
        event.target.onclick !== null ||
        Array.from(event.target.classList).includes('curzr-hover')) {
      this.hover(40)
    } else {
      this.hoverout()
    }
    
    this.pointerX = event.pageX + this.root.getBoundingClientRect().x
    this.pointerY = event.pageY + this.root.getBoundingClientRect().y
  
    this.cursor.style.transform = `translate3d(${this.pointerX}px, ${this.pointerY}px, 0)`
  }

  hover(radius) {
    this.cursor.style.width = this.cursor.style.height = `${radius}px`
    this.cursor.style.top = this.cursor.style.left = `${radius / -2}px`
  }

  hoverout() {
    this.cursor.style.width = this.cursor.style.height = `${this.cursorSize}px`
    this.cursor.style.top = this.cursor.style.left = `${this.cursorSize / -2}px`
  }

  click() {
    this.cursor.style.transform += ` scale(0.75)`
    setTimeout(() => {
      this.cursor.style.transform = this.cursor.style.transform.replace(` scale(0.75)`, '')
    }, 35)
  }

  hidden() {
    this.cursor.style.opacity = 0
    setTimeout(() => {
      this.cursor.setAttribute("hidden", "hidden")
    }, 500)
  }
}

class CircleAndDot {
  constructor() {
    this.root = document.body
    this.cursor = document.querySelector(".curzr-circle-and-dot")

    this.position = {
      distanceX: 0, 
      distanceY: 0,
      distance: 0,
      pointerX: 0,
      pointerY: 0,
    },
    this.previousPointerX = 0
    this.previousPointerY = 0
    this.angle = 0
    this.previousAngle = 0
    this.angleDisplace = 0
    this.degrees = 57.296
    this.cursorSize = 20
    this.fading = false

    this.cursorStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      top: `${ this.cursorSize / -2 }px`,
      left: `${ this.cursorSize / -2 }px`,
      zIndex: '2147483647',
      width: `${ this.cursorSize }px`,
      height: `${ this.cursorSize }px`,
      backgroundColor: '#fff0',
      border: '1.25px solid #292927',
      borderRadius: '50%',
      boxShadow: '0 -15px 0 -8px #292927',
      transition: '250ms, transform 100ms',
      userSelect: 'none',
      pointerEvents: 'none'
    }

    this.init(this.cursor, this.cursorStyle)
  }

  init(el, style) {
    Object.assign(el.style, style)
    setTimeout(() => {
      this.cursor.removeAttribute("hidden")
    }, 500)
    this.cursor.style.opacity = 1
  }

  move(event) {
    this.previousPointerX = this.position.pointerX
    this.previousPointerY = this.position.pointerY
    this.position.pointerX = event.pageX + this.root.getBoundingClientRect().x
    this.position.pointerY = event.pageY + this.root.getBoundingClientRect().y
    this.position.distanceX = this.previousPointerX - this.position.pointerX
    this.position.distanceY = this.previousPointerY - this.position.pointerY
    this.distance = Math.sqrt(this.position.distanceY ** 2 + this.position.distanceX ** 2)

    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.position.pointerX}px, ${this.position.pointerY}px, 0)`

    this.rotate(this.position)
    this.fade(this.distance)
  }

  rotate(position) {
    let unsortedAngle = Math.atan(Math.abs(position.distanceY) / Math.abs(position.distanceX)) * this.degrees
    this.previousAngle = this.angle

    if (position.distanceX <= 0 && position.distanceY >= 0) {
      this.angle = 90 - unsortedAngle + 0
    } else if (position.distanceX < 0 && position.distanceY < 0) {
      this.angle = unsortedAngle + 90
    } else if (position.distanceX >= 0 && position.distanceY <= 0) {
      this.angle = 90 - unsortedAngle + 180
    } else if (position.distanceX > 0 && position.distanceY > 0) {
      this.angle = unsortedAngle + 270
    }

    if (isNaN(this.angle)) {
      this.angle = this.previousAngle
    } else {
      if (this.angle - this.previousAngle <= -270) {
        this.angleDisplace += 360 + this.angle - this.previousAngle
      } else if (this.angle - this.previousAngle >= 270) {
        this.angleDisplace += this.angle - this.previousAngle - 360
      } else {
        this.angleDisplace += this.angle - this.previousAngle
      }
    }
    this.cursor.style.transform += ` rotate(${this.angleDisplace}deg)`
  }

  hover() {
    this.cursor.style.border = '10px solid #292927'
  }

  hoverout() {
    this.cursor.style.border = '1.25px solid #292927'
  }

  fade(distance) {
    this.cursor.style.boxShadow = `0 ${-15 - distance}px 0 -8px #292927`
    if (!this.fading) {
      this.fading = true
      setTimeout(() => {
        this.cursor.style.boxShadow = '0 -15px 0 -8px #29292700'
        this.fading = false
      }, 50)
    }
  }

  click() {
    this.cursor.style.transform += ` scale(0.75)`
    setTimeout(() => {
      this.cursor.style.transform = this.cursor.style.transform.replace(` scale(0.75)`, '')
    }, 35)
  }

  hidden() {
    this.cursor.style.opacity = 0
    setTimeout(() => {
      this.cursor.setAttribute("hidden", "hidden")
    }, 500)
  }
}

class GlitchEffect {
  constructor() {
    this.root = document.body
    this.cursor = document.querySelector(".curzr-glitch-effect")

    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 / -2 }px`,
      left: `${ this.cursorSize / -2 }px`,
      zIndex: '2147483647',
      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'
    }

    if (CSS.supports("backdrop-filter", "invert(1)")) {
      this.cursorStyle.backdropFilter = 'invert(1)'
      this.cursorStyle.backgroundColor = '#fff0'
    } else {
      this.cursorStyle.backgroundColor = '#222'
    }

    this.init(this.cursor, this.cursorStyle)
  }

  init(el, style) {
    Object.assign(el.style, style)
    setTimeout(() => {
      this.cursor.removeAttribute("hidden")
    }, 500)
    this.cursor.style.opacity = 1
    
  }

  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)

    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() {
    this.cursorSize = 30
  }

  hoverout() {
    this.cursorSize = 15
  }

  click() {
    this.cursor.style.transform += ` scale(0.75)`
    setTimeout(() => {
      this.cursor.style.transform = this.cursor.style.transform.replace(` scale(0.75)`, '')
    }, 35)
  }

  stop() {
    if (!this.moving) {
      this.moving = true
      setTimeout(() => {
        this.cursor.style.boxShadow = ''
        this.moving = false
      }, 50)
    }
  }

  hidden() {
    this.cursor.style.opacity = 0
    setTimeout(() => {
      this.cursor.setAttribute("hidden", "hidden")
    }, 500)
  }
}

class MotionBlur {
  constructor() {
    this.root = document.body
    this.cursor = document.querySelector(".curzr-motion")
    this.filter = document.querySelector(".curzr-motion .curzr-motion-blur")

    this.position = {
      distanceX: 0, 
      distanceY: 0,
      pointerX: 0,
      pointerY: 0,
    },
    this.previousPointerX = 0
    this.previousPointerY = 0
    this.angle = 0
    this.previousAngle = 0
    this.angleDisplace = 0
    this.degrees = 57.296
    this.cursorSize = 15
    this.moving = false

    this.cursorStyle = {
      boxSizing: 'border-box',
      position: 'fixed',
      top: `${ this.cursorSize / -2 }px`,
      left: `${ this.cursorSize / -2 }px`,
      zIndex: '2147483647',
      width: `${ this.cursorSize }px`,
      height: `${ this.cursorSize }px`,
      borderRadius: '50%',
      overflow: 'visible',
      transition: '200ms, transform 20ms',
      userSelect: 'none',
      pointerEvents: 'none'
    }

    this.init(this.cursor, this.cursorStyle)
  }

  init(el, style) {
    Object.assign(el.style, style)
    setTimeout(() => {
      this.cursor.removeAttribute("hidden")
    }, 500)
    this.cursor.style.opacity = 1
  }

  move(event) {
    this.previousPointerX = this.position.pointerX
    this.previousPointerY = this.position.pointerY
    this.position.pointerX = event.pageX + this.root.getBoundingClientRect().x
    this.position.pointerY = event.pageY + this.root.getBoundingClientRect().y
    this.position.distanceX = Math.min(Math.max(this.previousPointerX - this.position.pointerX, -20), 20)
    this.position.distanceY = Math.min(Math.max(this.previousPointerY - this.position.pointerY, -20), 20)

    this.cursor.style.transform = `translate3d(${this.position.pointerX}px, ${this.position.pointerY}px, 0)`
    this.rotate(this.position)
    this.moving ? this.stop() : this.moving = true
  }

  rotate(position) {
    let unsortedAngle = Math.atan(Math.abs(position.distanceY) / Math.abs(position.distanceX)) * this.degrees
    
    if (isNaN(unsortedAngle)) {
      this.angle = this.previousAngle
    } else {
      if (unsortedAngle <= 45) {
        if (position.distanceX * position.distanceY >= 0) {
          this.angle = +unsortedAngle
        } else {
          this.angle = -unsortedAngle
        }
        this.filter.setAttribute('stdDeviation', `${Math.abs(this.position.distanceX / 2)}, 0`)
      } else {
        if (position.distanceX * position.distanceY <= 0) {
          this.angle = 180 - unsortedAngle
        } else {
          this.angle = unsortedAngle
        }
        this.filter.setAttribute('stdDeviation', `${Math.abs(this.position.distanceY / 2)}, 0`)
      }
    }
    this.cursor.style.transform += ` rotate(${this.angle}deg)`
    this.previousAngle = this.angle
  }

  stop() {
    setTimeout(() => {
      this.filter.setAttribute('stdDeviation', '0, 0')
      this.moving = false
    }, 50)
  }

  hidden() {
    this.cursor.style.opacity = 0
    setTimeout(() => {
      this.cursor.setAttribute("hidden", "hidden")
    }, 500)
  }
}

let cursor = new ArrowPointer()
document.onmousemove = function (event) {
  cursor.move(event)
}
document.ontouchmove = function (event) {
  cursor.move(event.touches[0])
}
document.onclick = function () {
  if (typeof cursor.click === 'function') {
    cursor.click()
  }
}

I hope you did find this tutorial useful!

For more web development or UI/UX design tutorials, follow us on:

Other useful resources:

Alexandra Murtaza

Alexandra Murtaza