Improve canvas zooming and scrolling with mouse wheel for smoother interaction
Implement mouse wheel zooming and smooth scrolling for the canvas component, enhance WebSocket connection management with automatic reconnection and keep-alive pings, and refine CSS for pixel hover effects. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 0385ea33-cde8-4bbd-8fce-8d192d30eb41 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/870d08ce-da3b-4822-9874-c2fe2b7628b1/0385ea33-cde8-4bbd-8fce-8d192d30eb41/PVrRiEe
This commit is contained in:
@@ -57,6 +57,21 @@ export function Canvas({
|
||||
setZoom(1);
|
||||
};
|
||||
|
||||
const handleWheel = (e: React.WheelEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const zoomFactor = 1.1;
|
||||
const delta = e.deltaY;
|
||||
|
||||
if (delta < 0) {
|
||||
// Rein zoomen
|
||||
setZoom(prev => Math.min(prev * zoomFactor, 3));
|
||||
} else {
|
||||
// Raus zoomen
|
||||
setZoom(prev => Math.max(prev / zoomFactor, 0.5));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setPixelSize(Math.max(2, 8 * zoom));
|
||||
}, [zoom]);
|
||||
@@ -71,11 +86,12 @@ export function Canvas({
|
||||
<div className="flex-1 relative bg-canvas-bg overflow-hidden">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full h-full overflow-auto p-8"
|
||||
className="w-full h-full overflow-auto p-8 scroll-smooth canvas-container"
|
||||
onWheel={handleWheel}
|
||||
data-testid="canvas-container"
|
||||
>
|
||||
{/* Coordinate System Container */}
|
||||
<div className="relative inline-block">
|
||||
<div className="relative inline-block canvas-zoom">
|
||||
{/* Top X-axis coordinates */}
|
||||
<div className="flex ml-8 mb-1">
|
||||
{Array.from({ length: Math.ceil(canvasWidth / 10) }, (_, i) => (
|
||||
@@ -124,7 +140,7 @@ export function Canvas({
|
||||
<div
|
||||
key={`${x}-${y}`}
|
||||
className={cn(
|
||||
"pixel cursor-pointer transition-all duration-100 hover:scale-110 hover:z-10 absolute",
|
||||
"pixel cursor-pointer hover:scale-110 hover:z-10 absolute",
|
||||
cooldownActive && "cursor-not-allowed"
|
||||
)}
|
||||
style={{
|
||||
|
||||
@@ -5,15 +5,21 @@ export function useWebSocket(onMessage: (message: WSMessage) => void) {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [userCount, setUserCount] = useState(0);
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
const reconnectTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
const reconnectAttempts = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
const connect = () => {
|
||||
try {
|
||||
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws`;
|
||||
|
||||
console.log(`Connecting to WebSocket: ${wsUrl}`);
|
||||
ws.current = new WebSocket(wsUrl);
|
||||
|
||||
ws.current.onopen = () => {
|
||||
console.log("WebSocket connected");
|
||||
setIsConnected(true);
|
||||
reconnectAttempts.current = 0;
|
||||
};
|
||||
|
||||
ws.current.onmessage = (event) => {
|
||||
@@ -30,15 +36,38 @@ export function useWebSocket(onMessage: (message: WSMessage) => void) {
|
||||
}
|
||||
};
|
||||
|
||||
ws.current.onclose = () => {
|
||||
ws.current.onclose = (event) => {
|
||||
console.log("WebSocket disconnected:", event.code, event.reason);
|
||||
setIsConnected(false);
|
||||
|
||||
// Automatically reconnect after a delay
|
||||
if (reconnectAttempts.current < 5) {
|
||||
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000);
|
||||
console.log(`Reconnecting in ${delay}ms...`);
|
||||
reconnectTimer.current = setTimeout(() => {
|
||||
reconnectAttempts.current++;
|
||||
connect();
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
|
||||
ws.current.onerror = () => {
|
||||
ws.current.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
setIsConnected(false);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to create WebSocket connection:", error);
|
||||
setIsConnected(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
if (reconnectTimer.current) {
|
||||
clearTimeout(reconnectTimer.current);
|
||||
}
|
||||
if (ws.current) {
|
||||
ws.current.close();
|
||||
}
|
||||
|
||||
@@ -113,15 +113,7 @@
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.pixel {
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.pixel:hover {
|
||||
transform: scale(1.1);
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
}
|
||||
/* Entfernt - jetzt unten definiert */
|
||||
|
||||
|
||||
|
||||
@@ -139,6 +131,33 @@
|
||||
100% { background-position: 20px 20px; }
|
||||
}
|
||||
|
||||
/* Smooth scrolling für den gesamten Container */
|
||||
.scroll-smooth {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Canvas zoom transitions */
|
||||
.canvas-zoom {
|
||||
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* Geschmeidiges Mausrad-Scrolling */
|
||||
.canvas-container {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Optimierte Pixel-Hover-Effekte */
|
||||
.pixel {
|
||||
transition: transform 0.15s ease-out, box-shadow 0.15s ease-out;
|
||||
}
|
||||
|
||||
.pixel:hover {
|
||||
transform: scale(1.1);
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Toast animations */
|
||||
.toast-enter {
|
||||
animation: slideInRight 0.3s ease-out;
|
||||
|
||||
1
exports/canvas-2025-08-18T12-34-32-373Z.svg
Normal file
1
exports/canvas-2025-08-18T12-34-32-373Z.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#FFFFFF"/></svg>
|
||||
|
After Width: | Height: | Size: 120 B |
1
exports/canvas-2025-08-18T12-35-32-373Z.svg
Normal file
1
exports/canvas-2025-08-18T12-35-32-373Z.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#FFFFFF"/></svg>
|
||||
|
After Width: | Height: | Size: 120 B |
1
exports/canvas-2025-08-18T12-36-32-373Z.svg
Normal file
1
exports/canvas-2025-08-18T12-36-32-373Z.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#FFFFFF"/><rect x="94" y="69" width="1" height="1" fill="#be0039"/></svg>
|
||||
|
After Width: | Height: | Size: 177 B |
1
exports/canvas-2025-08-18T12-37-32-373Z.svg
Normal file
1
exports/canvas-2025-08-18T12-37-32-373Z.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#FFFFFF"/><rect x="94" y="69" width="1" height="1" fill="#be0039"/><rect x="94" y="68" width="1" height="1" fill="#6d001a"/></svg>
|
||||
|
After Width: | Height: | Size: 234 B |
@@ -128,20 +128,42 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
});
|
||||
}
|
||||
|
||||
wss.on('connection', (ws) => {
|
||||
wss.on('connection', (ws, req) => {
|
||||
console.log(`New WebSocket connection from ${req.socket.remoteAddress}`);
|
||||
connectedUsers.add(ws);
|
||||
broadcastUserCount();
|
||||
|
||||
ws.on('close', () => {
|
||||
// Send current user count immediately
|
||||
ws.send(JSON.stringify({
|
||||
type: "user_count",
|
||||
data: { count: connectedUsers.size },
|
||||
}));
|
||||
|
||||
ws.on('close', (code, reason) => {
|
||||
console.log(`WebSocket disconnected: ${code} ${reason}`);
|
||||
connectedUsers.delete(ws);
|
||||
broadcastUserCount();
|
||||
});
|
||||
|
||||
ws.on('error', () => {
|
||||
ws.on('error', (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
connectedUsers.delete(ws);
|
||||
broadcastUserCount();
|
||||
});
|
||||
|
||||
ws.on('pong', () => {
|
||||
// Keep connection alive
|
||||
});
|
||||
});
|
||||
|
||||
// Keep connections alive with ping/pong
|
||||
const pingInterval = setInterval(() => {
|
||||
connectedUsers.forEach(ws => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.ping();
|
||||
}
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
return httpServer;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user