Zurück zum Blog

Browser-Canvas-Spiele mobilfähig machen

Browser-Canvas-Spiele mobilfähig machen

Datum 2026-07-10
Kategorie Web Development
Tags Canvas, Mobile, Touch, JavaScript

Wer Browser-Spiele mit dem HTML5-Canvas-Element baut, denkt meist zuerst an Desktop und Tastatur. Wenn dieselben Spiele auch auf Tablets und Smartphones funktionieren sollen, braucht es ein paar gezielte Anpassungen. Dieser Post fasst zusammen, was dabei wirklich zählt.

Kontext: Acht selbstgebaute Mini-Spiele (Snake, Tetris, Space Invaders, Frogger, Asteroids und mehr) wurden nachträglich für mobile Geräte optimiert. Die Spiele waren ursprünglich reine Desktop-Tastatur-Spiele mit Canvas-Größen von 600×600 bis 800×600 Pixel.

1. Canvas CSS-Skalierung — ohne die Spiellogik anzufassen

Das Canvas-Element hat zwei unabhängige Größen: die logische Auflösung (die Attribute width und height im HTML) und die CSS-Anzeigegröße. Standardmäßig sind beide identisch, was auf kleinen Bildschirmen zu horizontalem Scrollen führt.

Die einfachste Lösung: Canvas per CSS skalieren, ohne die interne Auflösung zu ändern. Das lässt alle Spiellogik unberührt.

/* Canvas skaliert sich auf die Container-Breite,
   behält aber das Seitenverhältnis */
#game-container {
    max-width: 100%;
}
#game-container canvas {
    max-width: 100%;
    height: auto !important; /* !important überschreibt inline-Styles */
    display: block;
}
Wie es funktioniert: max-width: 100% schrumpft den Canvas auf die verfügbare Breite. height: auto erhält das Seitenverhältnis. Der Browser skaliert die Pixelausgabe — der Spielcode läuft weiterhin in der originalen Auflösung.

2. Der Koordinaten-Versatz-Bug bei Click-to-Place-Spielen

Spiele, bei denen man per Klick oder Tap etwas auf dem Canvas platziert, haben nach der CSS-Skalierung ein subtiles Problem: Die Touch-/Klick-Koordinaten des Events sind in CSS-Pixeln, aber der Spielcode erwartet Canvas-Logik-Pixel. Bei einem 800px breiten Canvas der auf 400px skaliert angezeigt wird, liegt jede Platzierung um den Faktor 2 versetzt.

Symptom: Man tippt auf die rechte Bildschirmhälfte, das Objekt erscheint in der Mitte — oder weiter links.

Fix: Den Scale-Faktor in die Koordinatenberechnung einbeziehen:

canvas.addEventListener('click', (e) => {
    const rect = canvas.getBoundingClientRect();

    // Scale-Faktor: wie viel Canvas-Pixel pro CSS-Pixel?
    const scaleX = canvas.width / rect.width;
    const scaleY = canvas.height / rect.height;

    // Korrekte Canvas-Koordinaten
    const x = (e.clientX - rect.left) * scaleX;
    const y = (e.clientY - rect.top)  * scaleY;

    // Ab hier wie gehabt...
    placeItemAt(x, y);
});
Merke: Dieser Fix ist auch für Touch-Events (touchstart) identisch — touch.clientX statt e.clientX, sonst gleiche Rechnung.

3. Touch-Controls für Tastatur-Spiele

Spiele die auf Pfeiltasten oder WASD angewiesen sind funktionieren auf Touchscreens ohne zusätzliche Steuerelemente gar nicht. Die Lösung: On-Screen-Buttons, die nur auf Touch-Geräten eingeblendet werden.

Die sauberste CSS-Methode nutzt pointer: coarse — das trifft Touch-Bildschirme ohne Hacks wie User-Agent-Erkennung:

/* Steuerung nur auf Touch-Geräten zeigen */
.touch-controls {
    display: none;
}
@media (pointer: coarse) {
    .touch-controls {
        display: grid; /* oder flex */
    }
}

Für Spiele mit kontinuierlichem Input (Schiff bewegt sich solange Taste gedrückt) müssen die Buttons den Tastaturzustand direkt simulieren:

/* Das Spiel liest: if (keys['ArrowLeft']) { ... } */
const keys = {};

document.querySelectorAll('[data-key]').forEach(btn => {
    const k = btn.dataset.key;

    btn.addEventListener('touchstart', e => {
        e.preventDefault(); // verhindert Scroll-Interferenz
        keys[k] = true;
        btn.classList.add('pressed');
    }, { passive: false });

    btn.addEventListener('touchend', () => {
        keys[k] = false;
        btn.classList.remove('pressed');
    });

    btn.addEventListener('touchcancel', () => {
        keys[k] = false;
        btn.classList.remove('pressed');
    });
});

Für Spiele mit diskretem Input (ein Schritt pro Tastendruck, wie Frogger) reicht ein einfacher Aufruf der bestehenden Input-Funktion auf touchstart:

btn.addEventListener('touchstart', e => {
    e.preventDefault();
    handleInput('ArrowUp'); // bestehende Spielfunktion
}, { passive: false });

4. Viewport-Höhe ohne Scrollen

Auf Mobilgeräten ist die sichtbare Fläche deutlich kleiner als die Canvas-Originalgröße. Damit kein vertikales Scrollen nötig ist, hilft eine kombinierte Strategie:

  • Margins und Padding des Game-Wrappers auf Mobile stark reduzieren
  • Canvas mit max-height nach unten deckeln
  • 100dvh statt 100vh verwenden — dvh berücksichtigt die Browser-Statusleiste korrekt
/* Canvas überschreitet nie die Viewport-Höhe */
#game-container canvas {
    max-height: calc(100dvh - 280px);
    /* 280px ≈ Header + Touch-Controls + Abstände */
}

@media (max-width: 768px) {
    #game-wrapper {
        padding: 0.5rem !important;
        margin: 0.25rem auto !important;
    }
}

5. Mobile-UX: Was weglassen, was ergänzen

Einige Desktop-Elemente sind auf Mobile überflüssig oder störend — andere fehlen:

  • Sidebar mit Stats ausblenden — Platz ist knapp, die Stats können bei Bedarf in einem kompakten Strip unter dem Canvas erscheinen
  • Start-Button ersetzen — wenn die Sidebar ausgeblendet wird, fehlt auch der Start-Button. Ein dedizierter mobiler Start-Button löst das:
    .mobile-start-btn { display: none; }
    @media (max-width: 768px) {
        .mobile-start-btn { display: flex; }
    }
  • Keyboard-Hinweise ausblenden — „Pfeiltasten zum Steuern" ist auf Touch sinnlos:
    @media (pointer: coarse) {
        .keyboard-hint { display: none; }
    }
  • Modales Overlay korrekt positionieren — ein Hilfe-Overlay mit position: fixed; top: 0 überdeckt den fixierten Header. Besser: top: 56px (Höhe des Headers), damit der Header weiterhin bedienbar bleibt

6. UX-Entscheidung: Keine doppelte Navigation

Ein „Zurück"-Button im Spiel-Header klingt intuitiv, ist aber überflüssig: Jeder Browser und jedes Betriebssystem bietet native Zurück-Navigation (Swipe, Hardware-Taste, Alt+←). Ein eigener Button belegt wertvollen Header-Platz und führt auf schmalen Displays zu Überlappungen.

Faustregel: Dinge nicht implementieren, die der Browser bereits mitbringt. Stattdessen den gewonnenen Platz für spielrelevante Elemente nutzen.

Zusammenfassung

Canvas-Spiele mobilfähig zu machen ist weniger aufwendig als es klingt, wenn man die richtigen Stellen kennt:

  1. CSS-Skalierung: max-width: 100%; height: auto auf dem Canvas — kein JS nötig, keine Spiellogik anfassen
  2. Koordinaten-Fix: scaleX = canvas.width / rect.width überall wo Klick- oder Touch-Positionen ins Canvas-Koordinatensystem umgerechnet werden
  3. Touch-Controls: @media (pointer: coarse) für On-Screen-Buttons, touchstart/end mit e.preventDefault() und passive: false
  4. Viewport-Fit: Margins reduzieren, max-height: calc(100dvh - Xpx) setzen
  5. UX aufräumen: Redundante Elemente entfernen, mobile Start-Button hinzufügen
Das Beste daran: Alle Änderungen sind additiv. Der Desktop-Betrieb bleibt unverändert — Tastatur, volle Canvas-Auflösung, Sidebar mit Stats. Mobile bekommt eine eigene, optimierte Erfahrung on top.