From 83fc03b31351cd97e294383c527393834f72d8d9 Mon Sep 17 00:00:00 2001 From: freesemar93 <46578442-freesemar93@users.noreply.replit.com> Date: Mon, 18 Aug 2025 12:40:00 +0000 Subject: [PATCH] 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 --- client/src/components/canvas.tsx | 22 ++++- client/src/hooks/use-websocket.tsx | 89 ++++++++++++++------- client/src/index.css | 37 ++++++--- exports/canvas-2025-08-18T12-34-32-373Z.svg | 1 + exports/canvas-2025-08-18T12-35-32-373Z.svg | 1 + exports/canvas-2025-08-18T12-36-32-373Z.svg | 1 + exports/canvas-2025-08-18T12-37-32-373Z.svg | 1 + server/routes.ts | 28 ++++++- 8 files changed, 135 insertions(+), 45 deletions(-) create mode 100644 exports/canvas-2025-08-18T12-34-32-373Z.svg create mode 100644 exports/canvas-2025-08-18T12-35-32-373Z.svg create mode 100644 exports/canvas-2025-08-18T12-36-32-373Z.svg create mode 100644 exports/canvas-2025-08-18T12-37-32-373Z.svg diff --git a/client/src/components/canvas.tsx b/client/src/components/canvas.tsx index 87c0b5b..8ec9277 100644 --- a/client/src/components/canvas.tsx +++ b/client/src/components/canvas.tsx @@ -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({
{/* Coordinate System Container */} -
+
{/* Top X-axis coordinates */}
{Array.from({ length: Math.ceil(canvasWidth / 10) }, (_, i) => ( @@ -124,7 +140,7 @@ export function Canvas({
void) { const [isConnected, setIsConnected] = useState(false); const [userCount, setUserCount] = useState(0); const ws = useRef(null); + const reconnectTimer = useRef(null); + const reconnectAttempts = useRef(0); + + 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) => { + try { + const message: WSMessage = JSON.parse(event.data); + + if (message.type === "user_count") { + setUserCount(message.data.count); + } + + onMessage(message); + } catch (error) { + console.error("Failed to parse WebSocket message:", error); + } + }; + + 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 = (error) => { + console.error("WebSocket error:", error); + setIsConnected(false); + }; + } catch (error) { + console.error("Failed to create WebSocket connection:", error); + setIsConnected(false); + } + }; useEffect(() => { - const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"; - const wsUrl = `${protocol}//${window.location.host}/ws`; - - ws.current = new WebSocket(wsUrl); - - ws.current.onopen = () => { - setIsConnected(true); - }; - - ws.current.onmessage = (event) => { - try { - const message: WSMessage = JSON.parse(event.data); - - if (message.type === "user_count") { - setUserCount(message.data.count); - } - - onMessage(message); - } catch (error) { - console.error("Failed to parse WebSocket message:", error); - } - }; - - ws.current.onclose = () => { - setIsConnected(false); - }; - - ws.current.onerror = () => { - setIsConnected(false); - }; + connect(); return () => { + if (reconnectTimer.current) { + clearTimeout(reconnectTimer.current); + } if (ws.current) { ws.current.close(); } diff --git a/client/src/index.css b/client/src/index.css index 9c43758..e56af89 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -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; diff --git a/exports/canvas-2025-08-18T12-34-32-373Z.svg b/exports/canvas-2025-08-18T12-34-32-373Z.svg new file mode 100644 index 0000000..d00a164 --- /dev/null +++ b/exports/canvas-2025-08-18T12-34-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exports/canvas-2025-08-18T12-35-32-373Z.svg b/exports/canvas-2025-08-18T12-35-32-373Z.svg new file mode 100644 index 0000000..d00a164 --- /dev/null +++ b/exports/canvas-2025-08-18T12-35-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exports/canvas-2025-08-18T12-36-32-373Z.svg b/exports/canvas-2025-08-18T12-36-32-373Z.svg new file mode 100644 index 0000000..6bdc3c1 --- /dev/null +++ b/exports/canvas-2025-08-18T12-36-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/exports/canvas-2025-08-18T12-37-32-373Z.svg b/exports/canvas-2025-08-18T12-37-32-373Z.svg new file mode 100644 index 0000000..0d6ad06 --- /dev/null +++ b/exports/canvas-2025-08-18T12-37-32-373Z.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/routes.ts b/server/routes.ts index 188cab4..4f1d37f 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -128,20 +128,42 @@ export async function registerRoutes(app: Express): Promise { }); } - 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; }