reactbits-mcp-server
Version:
MCP Server for React Bits - Access 99+ React components with animations, backgrounds, and UI elements
126 lines (106 loc) • 3.38 kB
JSX
import { useRef, useEffect, useState } from 'react';
import { gsap } from 'gsap';
import './PixelTransition.css';
function PixelTransition({
firstContent,
secondContent,
gridSize = 7,
pixelColor = 'currentColor',
animationStepDuration = 0.3,
className = '',
style = {},
aspectRatio = '100%',
}) {
const containerRef = useRef(null);
const pixelGridRef = useRef(null);
const activeRef = useRef(null);
const delayedCallRef = useRef(null);
const [isActive, setIsActive] = useState(false);
const isTouchDevice =
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
window.matchMedia('(pointer: coarse)').matches;
useEffect(() => {
const pixelGridEl = pixelGridRef.current;
if (!pixelGridEl) return;
pixelGridEl.innerHTML = '';
for (let row = 0; row < gridSize; row++) {
for (let col = 0; col < gridSize; col++) {
const pixel = document.createElement('div');
pixel.classList.add('pixelated-image-card__pixel');
pixel.style.backgroundColor = pixelColor;
const size = 100 / gridSize;
pixel.style.width = `${size}%`;
pixel.style.height = `${size}%`;
pixel.style.left = `${col * size}%`;
pixel.style.top = `${row * size}%`;
pixelGridEl.appendChild(pixel);
}
}
}, [gridSize, pixelColor]);
const animatePixels = (activate) => {
setIsActive(activate);
const pixelGridEl = pixelGridRef.current;
const activeEl = activeRef.current;
if (!pixelGridEl || !activeEl) return;
const pixels = pixelGridEl.querySelectorAll('.pixelated-image-card__pixel');
if (!pixels.length) return;
gsap.killTweensOf(pixels);
if (delayedCallRef.current) {
delayedCallRef.current.kill();
}
gsap.set(pixels, { display: 'none' });
const totalPixels = pixels.length;
const staggerDuration = animationStepDuration / totalPixels;
gsap.to(pixels, {
display: 'block',
duration: 0,
stagger: {
each: staggerDuration,
from: 'random'
}
});
delayedCallRef.current = gsap.delayedCall(animationStepDuration, () => {
activeEl.style.display = activate ? 'block' : 'none';
activeEl.style.pointerEvents = activate ? 'none' : '';
});
gsap.to(pixels, {
display: 'none',
duration: 0,
delay: animationStepDuration,
stagger: {
each: staggerDuration,
from: 'random'
}
});
};
const handleMouseEnter = () => {
if (!isActive) animatePixels(true);
};
const handleMouseLeave = () => {
if (isActive) animatePixels(false);
};
const handleClick = () => {
animatePixels(!isActive);
};
return (
<div
ref={containerRef}
className={`pixelated-image-card ${className}`}
style={style}
onMouseEnter={!isTouchDevice ? handleMouseEnter : undefined}
onMouseLeave={!isTouchDevice ? handleMouseLeave : undefined}
onClick={isTouchDevice ? handleClick : undefined}
>
<div style={{ paddingTop: aspectRatio }} />
<div className="pixelated-image-card__default">
{firstContent}
</div>
<div className="pixelated-image-card__active" ref={activeRef}>
{secondContent}
</div>
<div className="pixelated-image-card__pixels" ref={pixelGridRef} />
</div>
);
}
export default PixelTransition;