Shopify themes, liquid, logos, and UX
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>
Solved! Go to the solution
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>
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
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.
Hi @Eralys,
Please send me the link that is not working, I will check it
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);
}
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...
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>
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.
Learn how to build powerful custom workflows in Shopify Flow with expert guidance from ...
By Jacqui May 7, 2025Did You Know? May is named after Maia, the Roman goddess of growth and flourishing! ...
By JasonH May 2, 2025Discover opportunities to improve SEO with new guidance available from Shopify’s growth...
By Jacqui May 1, 2025