How to implement animated cursor into existing shopify theme

Solved

How to implement animated cursor into existing shopify theme

Eralys
Tourist
4 0 1

I have been trying for hours but can't seem to get it to work, either my cursor entire disappears or the original cursor is there but only when hovering over clickable boxes.... I tried multiple ways, but something keeps on overwriting or clashing with my code. anyone who can help?

I tried to seclude my code entirely from shopify's codes by making a clean page and purely adding the cursor code in and there it works perfect.... (unless you change layout from; none to theme --> theme.liquid)

code on the clean page:

 

{% layout none %}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Firefly Cursor Test</title>
  <style>
    html, body {
      margin: 0;
      height: 100%;
      background: #111;
      cursor: none;
      overflow: hidden;
    }

    #cursor {
      position: fixed;
      width: 10px;
      height: 10px;
      background: white;
      border-radius: 50%;
      transform: translate(-50%, -50%);
      pointer-events: none;
      z-index: 9999;
    }

    .sparkle {
      position: fixed;
      width: 6px;
      height: 6px;
      background: rgba(255,255,255,0.8);
      border-radius: 50%;
      pointer-events: none;
      z-index: 9998;
      animation: sparkleFloat 1.8s ease-out forwards;
    }

    @keyframes sparkleFloat {
      0% {
        transform: translateY(0) scale(1);
        opacity: 1;
      }
      100% {
        transform: translateY(-40px) scale(0.3);
        opacity: 0;
      }
    }
  </style>
</head>
<body>
  <div id="cursor"></div>

  <script>
    const cursor = document.getElementById("cursor");
    let mouseX = window.innerWidth / 2;
    let mouseY = window.innerHeight / 2;

    document.addEventListener("mousemove", e => {
      mouseX = e.clientX;
      mouseY = e.clientY;
    });

    function renderCursor() {
      cursor.style.left = mouseX + "px";
      cursor.style.top = mouseY + "px";
      requestAnimationFrame(renderCursor);
    }

    renderCursor();

    function spawnSparkle() {
      const sparkle = document.createElement("div");
      sparkle.className = "sparkle";
      sparkle.style.left = (mouseX + (Math.random() * 20 - 10)) + "px";
      sparkle.style.top = (mouseY + (Math.random() * 20 - 10)) + "px";
      document.body.appendChild(sparkle);
      setTimeout(() => sparkle.remove(), 1800);
    }

    function sparkleLoop() {
      spawnSparkle();
      setTimeout(sparkleLoop, 100 + Math.random() * 100);
    }

    sparkleLoop();
  </script>
</body>
</html>

 

Accepted Solution (1)
tim
Shopify Partner
4497 535 1640

This is an accepted solution.

Of course -- taken straight from the "Custom Liquid" section:

<style>

* {
  cursor: none !important;
}

#cursor {
  position: fixed;
  width: 10px;
  height: 10px;
  background: white;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 9999;
}

.sparkle {
  position: fixed;
  width: 6px;
  height: 6px;
  background: rgba(255,255,255,0.8);
  border-radius: 50%;
  pointer-events: none;
  z-index: 9998;
  animation: sparkleFloat 1.8s ease-out forwards;
}

#cursor, .sparkle {
  display: block !important;
  border: 1px solid black;
}

#cursor.interactive {
  background: red;
  transform: translate(-50%, -50%) scale(1.5);
}

@keyframes sparkleFloat {
  0% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
  100% {
    transform: translateY(-40px) scale(0.3);
    opacity: 0;
  }
}
</style>

<div id="cursor"></div>

<script>
const cursor = document.getElementById("cursor");
let mouseX = window.innerWidth / 2;
let mouseY = window.innerHeight / 2;

document.addEventListener("mousemove", e => {
  mouseX = e.clientX;
  mouseY = e.clientY;
});

function renderCursor() {
  cursor.style.left = mouseX + "px";
  cursor.style.top = mouseY + "px";
  requestAnimationFrame(renderCursor);
}

renderCursor();

function spawnSparkle() {
  const sparkle = document.createElement("div");
  sparkle.className = "sparkle";
  sparkle.style.left = (mouseX + (Math.random() * 20 - 10)) + "px";
  sparkle.style.top = (mouseY + (Math.random() * 20 - 10)) + "px";
  document.body.appendChild(sparkle);
  setTimeout(() => sparkle.remove(), 1800);
}

function sparkleLoop() {
  spawnSparkle();
  setTimeout(sparkleLoop, 100 + Math.random() * 100);
}

sparkleLoop();

const interactable = "a, summary, input, button, select, textarea, [role=button]";
document.body.addEventListener('mouseenter', (evt) => {
  if ( evt.target.matches(interactable)) {
    cursor.classList.add('interactive');
  }
}, { passive: true, capture: true});

document.body.addEventListener('mouseleave', (evt) => {
  if ( evt.target.matches(interactable)) {
    cursor.classList.remove('interactive');
  }
}, { passive: true, capture: true});
</script>

 

If my post is helpful, hit the thumb up button -- it will help others with similar problem to find a solution.
I can be reached via e-mail tairli@yahoo.com

View solution in original post

Replies 7 (7)

TheScriptFlow
Shopify Partner
709 49 95

You are doing great. But I found typos in your code which I fix it in the updated code.

Replace the previous code with this one.

<style>
  html, body, a, button, input, select, textarea, [role="button"] {
    cursor: none !important;
  }
  
  #custom-cursor {
    position: fixed;
    width: 10px;
    height: 10px;
    background: white;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    pointer-events: none;
    z-index: 9999;
    mix-blend-mode: difference;
  }

  .cursor-sparkle {
    position: fixed;
    width: 6px;
    height: 6px;
    background: rgba(255,255,255,0.8);
    border-radius: 50%;
    pointer-events: none;
    z-index: 9998;
    animation: sparkleFloat 1.8s ease-out forwards;
  }

  @keyframes sparkleFloat {
    0% { transform: translateY(0) scale(1); opacity: 1; }
    100% { transform: translateY(-40px) scale(0.3); opacity: 0; }
  }
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
  const cursor = document.createElement('div');
  cursor.id = 'custom-cursor';
  document.body.appendChild(cursor);
  
  let mouseX = window.innerWidth / 2;
  let mouseY = window.innerHeight / 2;
  let cursorVisible = true;

  document.addEventListener("mousemove", e => {
    mouseX = e.clientX;
    mouseY = e.clientY;
    if (!cursorVisible) {
      cursor.style.display = 'block';
      cursorVisible = true;
    }
  });

  document.addEventListener("mouseleave", () => {
    cursor.style.display = 'none';
    cursorVisible = false;
  });

  function renderCursor() {
    cursor.style.left = mouseX + "px";
    cursor.style.top = mouseY + "px";
    requestAnimationFrame(renderCursor);
  }
  renderCursor();

  function spawnSparkle() {
    if (!cursorVisible) return;
    
    const sparkle = document.createElement("div");
    sparkle.className = "cursor-sparkle";
    sparkle.style.left = (mouseX + (Math.random() * 20 - 10)) + "px";
    sparkle.style.top = (mouseY + (Math.random() * 20 - 10)) + "px";
    document.body.appendChild(sparkle);
    setTimeout(() => sparkle.remove(), 1800);
  }

  function sparkleLoop() {
    spawnSparkle();
    setTimeout(sparkleLoop, 100 + Math.random() * 100);
  }
  sparkleLoop();

  const interactiveElements = ['a', 'button', 'input', 'select', 'textarea', '[role="button"]'];
  interactiveElements.forEach(selector => {
    document.querySelectorAll(selector).forEach(el => {
      el.addEventListener('mouseenter', () => {
        cursor.style.transform = 'translate(-50%, -50%) scale(1.5)';
      });
      el.addEventListener('mouseleave', () => {
        cursor.style.transform = 'translate(-50%, -50%) scale(1)';
      });
    });
  });
});
</script>

Let me know if you need more assistance.

Thanks

- Need a Shopify Specialist? Chat on WhatsApp Or Email at info@thescriptflow.com

- Boost Your Sales with Affiliate Marketing - UpPromote: Affiliate & Referral


- If my solution was helpful, mark it as a solution and hit the like button! And Wait Don't forget to Buy me a Coffee

Eralys
Tourist
4 0 1

thank you for editing it for me,
My original code works perfect on an empty page so I hope my typos weren't to bad 😅 (it's been a while since I last coded websites as I am more into data now a days 🤣)

 

I must add tho - that if I implement your code although it does make the cursor appear (which I originally struggled with. so, thank you for that) the sparkle effect doesn't show up anymore and it's very laggy, Read --> it doesn't directly show on the page and you need to move your mouse around for good 5-10 second before it appears.

namphan
Shopify Partner
2690 349 399

Hi @Eralys,

Please send me the link that is not working, I will check it

Coffee tips fuels my dedication.
Shopify Development Service
PageFly Page Builder Optimize your Shopify store (Free plan available)
Need help with your store? namphan992@gmail.com

tim
Shopify Partner
4497 535 1640

Couple of points:

 

1. I guess you may be using Dawn.
Modern themes by Shopify has this CSS rule https://github.com/Shopify/dawn/blob/main/assets/base.css#L468-L482 

div:empty,
section:empty,
article:empty,
p:empty,
h1:empty,
h2:empty,
h3:empty,
h4:empty,
h5:empty,
h6:empty {
  display: none;
}

which sets display: none; on empty element and yours are empty, so they are not shown.

So you need a rule to override this:

#cursor, .sparkle {
  display: block !important;
}

 

2. You have 

    html, body {
       .. 
      cursor: none;
    }

but every interactive element has it's own cursor and it overrides your rule.

So you should rather use something like 

* {
  cursor: none !important;
}

If you implement these, your code will work. 

See how it works in my test store (i've just pasted your code with my edits into "Custom Liquid" section in footer (removed body and html tags, of course))

https://3q1i7alo0l2asagx-23104437.shopifypreview.com 

 

 

3. It's a good idea to still reflect in the cursor that it's over interactive element, so this part of @TheScriptFlow code worth considering:

  const interactiveElements = ['a', 'button', 'input', 'select', 'textarea', '[role="button"]'];
  interactiveElements.forEach(selector => {
    document.querySelectorAll(selector).forEach(el => {
      el.addEventListener('mouseenter', () => {
        cursor.style.transform = 'translate(-50%, -50%) scale(1.5)';
      });
      el.addEventListener('mouseleave', () => {
        cursor.style.transform = 'translate(-50%, -50%) scale(1)';
      });
    });
  });

However, I'd rather use just a pair of event listeners on the body:

const interactable = "a, summary, input, button, select, textarea, [role=button]";
document.body.addEventListener('mouseenter', (evt) => {
  if ( evt.target.matches(interactable)) {
    cursor.classList.add('interactive');
  }
}, { passive: true, capture: true});

document.body.addEventListener('mouseleave', (evt) => {
  if ( evt.target.matches(interactable)) {
    cursor.classList.remove('interactive');
  }
}, { passive: true, capture: true});

and add a rule like:

#cursor.interactive {
  background: red;
  transform: translate(-50%, -50%) scale(1.5);
}
If my post is helpful, hit the thumb up button -- it will help others with similar problem to find a solution.
I can be reached via e-mail tairli@yahoo.com
Eralys
Tourist
4 0 1

Could you show me the full code how it should look like, maybe, if that isn't a hassle for you? I tried implementing my own code with the adjustments you provided and it still fails me, so I assume I made a slight error myself which my fried brain just can't seem to find, seeing as it indeed works perfect on the link you provided...

tim
Shopify Partner
4497 535 1640

This is an accepted solution.

Of course -- taken straight from the "Custom Liquid" section:

<style>

* {
  cursor: none !important;
}

#cursor {
  position: fixed;
  width: 10px;
  height: 10px;
  background: white;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 9999;
}

.sparkle {
  position: fixed;
  width: 6px;
  height: 6px;
  background: rgba(255,255,255,0.8);
  border-radius: 50%;
  pointer-events: none;
  z-index: 9998;
  animation: sparkleFloat 1.8s ease-out forwards;
}

#cursor, .sparkle {
  display: block !important;
  border: 1px solid black;
}

#cursor.interactive {
  background: red;
  transform: translate(-50%, -50%) scale(1.5);
}

@keyframes sparkleFloat {
  0% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
  100% {
    transform: translateY(-40px) scale(0.3);
    opacity: 0;
  }
}
</style>

<div id="cursor"></div>

<script>
const cursor = document.getElementById("cursor");
let mouseX = window.innerWidth / 2;
let mouseY = window.innerHeight / 2;

document.addEventListener("mousemove", e => {
  mouseX = e.clientX;
  mouseY = e.clientY;
});

function renderCursor() {
  cursor.style.left = mouseX + "px";
  cursor.style.top = mouseY + "px";
  requestAnimationFrame(renderCursor);
}

renderCursor();

function spawnSparkle() {
  const sparkle = document.createElement("div");
  sparkle.className = "sparkle";
  sparkle.style.left = (mouseX + (Math.random() * 20 - 10)) + "px";
  sparkle.style.top = (mouseY + (Math.random() * 20 - 10)) + "px";
  document.body.appendChild(sparkle);
  setTimeout(() => sparkle.remove(), 1800);
}

function sparkleLoop() {
  spawnSparkle();
  setTimeout(sparkleLoop, 100 + Math.random() * 100);
}

sparkleLoop();

const interactable = "a, summary, input, button, select, textarea, [role=button]";
document.body.addEventListener('mouseenter', (evt) => {
  if ( evt.target.matches(interactable)) {
    cursor.classList.add('interactive');
  }
}, { passive: true, capture: true});

document.body.addEventListener('mouseleave', (evt) => {
  if ( evt.target.matches(interactable)) {
    cursor.classList.remove('interactive');
  }
}, { passive: true, capture: true});
</script>

 

If my post is helpful, hit the thumb up button -- it will help others with similar problem to find a solution.
I can be reached via e-mail tairli@yahoo.com
Eralys
Tourist
4 0 1

Thanks allot! it works now 🙂
Also thank you for the time for the extra explanation.
last time I did HTML, CSS and JS is long ago, my skills watered down allot apparently, luckily my understanding of code not entirely seeing as I do other languages haha, so the explanation was a nice refresher for me.