mirror of
https://github.com/timmypidashev/web.git
synced 2026-04-14 11:03:50 +00:00
Add cursor trail; fix giscuss
This commit is contained in:
@@ -7,6 +7,16 @@ interface CursorState {
|
|||||||
isClicking: boolean;
|
isClicking: boolean;
|
||||||
isOverBackground: boolean;
|
isOverBackground: boolean;
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
|
isOverGiscus: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TrailPoint {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
id: number;
|
||||||
|
color: string;
|
||||||
|
opacity: number;
|
||||||
|
scale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Cursor: React.FC = () => {
|
const Cursor: React.FC = () => {
|
||||||
@@ -16,13 +26,17 @@ const Cursor: React.FC = () => {
|
|||||||
isPointer: false,
|
isPointer: false,
|
||||||
isClicking: false,
|
isClicking: false,
|
||||||
isOverBackground: false,
|
isOverBackground: false,
|
||||||
isMobile: false
|
isMobile: false,
|
||||||
|
isOverGiscus: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [trail, setTrail] = useState<TrailPoint[]>([]);
|
||||||
const cursorRef = useRef<HTMLDivElement>(null);
|
const cursorRef = useRef<HTMLDivElement>(null);
|
||||||
const requestRef = useRef<number>();
|
const requestRef = useRef<number>();
|
||||||
const targetX = useRef(0);
|
const targetX = useRef(0);
|
||||||
const targetY = useRef(0);
|
const targetY = useRef(0);
|
||||||
|
const trailIdCounter = useRef(0);
|
||||||
|
const lastTrailTime = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if device is mobile or tablet
|
// Check if device is mobile or tablet
|
||||||
@@ -37,43 +51,6 @@ const Cursor: React.FC = () => {
|
|||||||
|
|
||||||
// Add resize listener to detect device changes
|
// Add resize listener to detect device changes
|
||||||
window.addEventListener('resize', checkMobile);
|
window.addEventListener('resize', checkMobile);
|
||||||
|
|
||||||
// Function to inject styles into shadow DOM
|
|
||||||
const injectShadowStyles = () => {
|
|
||||||
const giscusElements = document.querySelectorAll('.giscus-frame');
|
|
||||||
giscusElements.forEach(element => {
|
|
||||||
if (element.shadowRoot) {
|
|
||||||
// Check if we already injected styles
|
|
||||||
if (!element.shadowRoot.querySelector('#custom-cursor-style')) {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = 'custom-cursor-style';
|
|
||||||
style.textContent = `
|
|
||||||
* {
|
|
||||||
cursor: none !important;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
element.shadowRoot.appendChild(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch for Giscus loading
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.addedNodes.length) {
|
|
||||||
injectShadowStyles();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(document.body, {
|
|
||||||
childList: true,
|
|
||||||
subtree: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initial injection
|
|
||||||
injectShadowStyles();
|
|
||||||
|
|
||||||
const updateCursorPosition = (e: MouseEvent) => {
|
const updateCursorPosition = (e: MouseEvent) => {
|
||||||
targetX.current = e.clientX;
|
targetX.current = e.clientX;
|
||||||
@@ -81,6 +58,12 @@ const Cursor: React.FC = () => {
|
|||||||
|
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
|
|
||||||
|
// Check if we're over Giscus
|
||||||
|
const isOverGiscus = target.closest('.giscus') !== null ||
|
||||||
|
target.closest('#inject-comments') !== null ||
|
||||||
|
target.closest('.giscus-frame') !== null ||
|
||||||
|
target.closest('iframe') !== null;
|
||||||
|
|
||||||
// Check if the element is interactive
|
// Check if the element is interactive
|
||||||
const isInteractive = target.tagName === 'A' || target.tagName === 'BUTTON' ||
|
const isInteractive = target.tagName === 'A' || target.tagName === 'BUTTON' ||
|
||||||
target.closest('a') !== null || target.closest('button') !== null ||
|
target.closest('a') !== null || target.closest('button') !== null ||
|
||||||
@@ -92,8 +75,30 @@ const Cursor: React.FC = () => {
|
|||||||
y: e.clientY,
|
y: e.clientY,
|
||||||
isPointer: isInteractive,
|
isPointer: isInteractive,
|
||||||
isOverBackground: target.tagName === 'CANVAS' ||
|
isOverBackground: target.tagName === 'CANVAS' ||
|
||||||
target.closest('canvas') !== null
|
target.closest('canvas') !== null,
|
||||||
|
isOverGiscus: isOverGiscus
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Add trail points
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastTrailTime.current > 10) { // Control trail density
|
||||||
|
const cursorColor = getCursorColor();
|
||||||
|
setTrail(prevTrail => {
|
||||||
|
const newTrail = [
|
||||||
|
{
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY,
|
||||||
|
id: trailIdCounter.current++,
|
||||||
|
color: cursorColor,
|
||||||
|
opacity: 0.8,
|
||||||
|
scale: 1
|
||||||
|
},
|
||||||
|
...prevTrail.slice(0, 20) // Keep last 20 points
|
||||||
|
];
|
||||||
|
return newTrail;
|
||||||
|
});
|
||||||
|
lastTrailTime.current = now;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
@@ -106,6 +111,7 @@ const Cursor: React.FC = () => {
|
|||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
setState(prev => ({ ...prev, x: -100, y: -100, isClicking: false }));
|
setState(prev => ({ ...prev, x: -100, y: -100, isClicking: false }));
|
||||||
|
setTrail([]); // Clear trail when mouse leaves
|
||||||
};
|
};
|
||||||
|
|
||||||
// Smooth cursor movement animation
|
// Smooth cursor movement animation
|
||||||
@@ -121,6 +127,16 @@ const Cursor: React.FC = () => {
|
|||||||
cursorRef.current.style.left = newX + 'px';
|
cursorRef.current.style.left = newX + 'px';
|
||||||
cursorRef.current.style.top = newY + 'px';
|
cursorRef.current.style.top = newY + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update trail points
|
||||||
|
setTrail(prevTrail =>
|
||||||
|
prevTrail.map(point => ({
|
||||||
|
...point,
|
||||||
|
opacity: point.opacity * 0.95, // Fade out
|
||||||
|
scale: point.scale * 0.97 // Shrink
|
||||||
|
})).filter(point => point.opacity > 0.01) // Remove faded points
|
||||||
|
);
|
||||||
|
|
||||||
requestRef.current = requestAnimationFrame(animate);
|
requestRef.current = requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,7 +158,6 @@ const Cursor: React.FC = () => {
|
|||||||
if (requestRef.current) {
|
if (requestRef.current) {
|
||||||
cancelAnimationFrame(requestRef.current);
|
cancelAnimationFrame(requestRef.current);
|
||||||
}
|
}
|
||||||
observer.disconnect();
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -215,17 +230,42 @@ const Cursor: React.FC = () => {
|
|||||||
|
|
||||||
return '#ebdbb2'; // default foreground color
|
return '#ebdbb2'; // default foreground color
|
||||||
};
|
};
|
||||||
|
|
||||||
const cursorColor = getCursorColor();
|
const cursorColor = getCursorColor();
|
||||||
const scale = state.isClicking ? 0.8 : (state.isPointer ? 1.2 : 1);
|
const scale = state.isClicking ? 0.8 : (state.isPointer ? 1.2 : 1);
|
||||||
|
|
||||||
// Hide cursor completely on mobile
|
// Hide cursor completely on mobile or when over Giscus
|
||||||
if (state.isMobile) {
|
if (state.isMobile || state.isOverGiscus) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* Trail effect */}
|
||||||
|
{trail.map(point => (
|
||||||
|
<div
|
||||||
|
key={point.id}
|
||||||
|
className="pointer-events-none fixed z-[9997]"
|
||||||
|
style={{
|
||||||
|
left: point.x,
|
||||||
|
top: point.y,
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
opacity: point.opacity,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="rounded-full"
|
||||||
|
style={{
|
||||||
|
width: `${8 * point.scale}px`,
|
||||||
|
height: `${8 * point.scale}px`,
|
||||||
|
backgroundColor: point.color,
|
||||||
|
boxShadow: `0 0 ${4 * point.scale}px ${point.color}`,
|
||||||
|
filter: 'blur(1px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Main cursor dot */}
|
{/* Main cursor dot */}
|
||||||
<div
|
<div
|
||||||
ref={cursorRef}
|
ref={cursorRef}
|
||||||
|
|||||||
@@ -4,9 +4,14 @@
|
|||||||
|
|
||||||
/* Hide default cursor globally on desktop */
|
/* Hide default cursor globally on desktop */
|
||||||
@media (min-width: 1025px) {
|
@media (min-width: 1025px) {
|
||||||
* {
|
*:not(.giscus):not(.giscus *):not(.giscus-frame):not(.giscus-frame *):not(iframe):not(iframe *) {
|
||||||
cursor: none !important;
|
cursor: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Allow default cursor for Giscus */
|
||||||
|
.giscus, .giscus *, .giscus-frame, .giscus-frame *, #inject-comments, #inject-comments *, iframe, iframe * {
|
||||||
|
cursor: auto !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show default cursor on mobile and tablet */
|
/* Show default cursor on mobile and tablet */
|
||||||
|
|||||||
Reference in New Issue
Block a user