Showcase Video#

SDC Component#
cursor-animated-custom.component.yml
1
2
3
4
5
| $schema: https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json
name: cursor-opc
status: experimental
description: 'A custom cursor component that follows mouse movement with smooth animation'
props: {}
|
cursor-animated-custom.twig
1
2
3
| <div id="cursor-custom" class="cursor-custom">
<div class="cursor-custom__dot"></div>
</div>
|
_cursor-animated-custom.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| /**
* Custom cursor functionality
*/
(function () {
'use strict';
// Initialize once DOM is fully loaded
document.addEventListener('DOMContentLoaded', () => {
const cursor = document.querySelector('.cursor-custom__dot');
if (!cursor) return;
let mouseX = 0;
let mouseY = 0;
let cursorX = 0;
let cursorY = 0;
// Smooth animation function
const animate = () => {
const diffX = mouseX - cursorX;
const diffY = mouseY - cursorY;
cursorX += diffX * 0.1;
cursorY += diffY * 0.1;
// Use translate3d for better performance and combine with scale if active
const scale = cursor.classList.contains('is-active-dot') ? 'scale(2)' : 'scale(1)';
cursor.style.transform = `translate3d(${cursorX}px, ${cursorY}px, 0) ${scale}`;
requestAnimationFrame(animate);
};
// Track mouse movement
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
// Handle cursor scale/color on clickable elements
document.querySelectorAll('a, button, [role="button"], input[type="submit"]').forEach(el => {
el.addEventListener('mouseenter', () => cursor.classList.add('is-active-dot'));
el.addEventListener('mouseleave', () => cursor.classList.remove('is-active-dot'));
});
// Handle cursor on hover of the iframe
document.querySelectorAll('iframe').forEach(el => {
el.addEventListener('mouseenter', () => cursor.classList.add('is-dot-in-iframe'));
el.addEventListener('mouseleave', () => cursor.classList.remove('is-dot-in-iframe'));
});
// Start animation
animate();
});
})();
|
cursor-animated-custom.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // Hide the default cursor
body:has(#cursor-custom) {
cursor: visible !important;
// Base cursor container
#cursor-custom {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
pointer-events: none;
mix-blend-mode: difference;
// Cursor dot inside the cursor-custom container
&>div.cursor-custom__dot {
position: fixed;
top: -15px;
left: -15px;
width: 20px;
height: 20px;
background-color: white;
border-radius: 50%;
transform-origin: center;
transition: transform 0.05s ease-out, background-color 0.5s ease-out;
will-change: transform;
&.is-active-dot { background-color: rgb(46, 46, 46); }
&.is-dot-in-iframe {background-color: transparent !important;}
}
}
}
|
And then simply chuck the following in your page.html.twig
or other template where relevant
1
| +{% include 'radix_opc:cursor-opc' %}
|
Reference#
https://codepen.io/seiko-yamaguchi/pen/gORrrKP