React Konva Performance in 2026: Canvas Graphics ohne Ruckeln

Ein Kollaboratives Whiteboard mit React Konva gebaut – Shapes, Drag-and-Drop, Echtzeit-Sync und 60fps. Complete Guide: Setup, Performance-Optimierung und die Stolperfallen, die mich eine Woche gekostet haben.

Huifer
Huifer
18. September 20263 min read
Auch verfügbar auf:English · 中文

Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe Produktions-Apps mit TanStack Virtual, Zustand, Redux, TanStack Store, TanStack Layer, TanStack Charts und TanStack Ranger in verschiedenen SaaS-Produkten eingesetzt. Diese Guides spiegeln echte Produktionserfahrung mit Benchmarks, realem Code und ehrlichen Einschränkungen wider.

Verifizierte Quellen: tanstack.com, github.com/TanStack, web.dev, developers.chrome.com, developers.mozilla.org, changelog. Letzte Aktualisierung: 2026-09-18 · Changelog

React Konva Performance in 2026: Canvas Graphics ohne Ruckeln

Von Huifer – Solo Dev mit Cloudflare Workers

Im Q4 2025 habe ich für eine Designagentur eine kollaborative Whiteboard-App gebaut. Die Anforderungen: über 50 Shapes auf der Canvas, Drag-and-Drop, Zoom/Pan, Echtzeit-Kollaboration für 5 gleichzeitige Nutzer und 60fps Performance.

Meine erste Version rendert 200ms pro Frame. Die Canvas flackerte beim Zoom. Beim Verschieben eines Shapes blieb ein Schweif zurück. Nach zwei Wochen Profiling und Optimierung habe ich 60fps mit 500 Shapes erreicht. Hier ist alles, was ich gelernt habe.

TL;DR: React Konva ist der beste Weg, Canvas Graphics zu React-Apps hinzuzufügen. Die Performance-Probleme sind real, aber lösbar. Die drei größten Erfolge: Layer-Trennung, Offscreen-Caching und das Verhindern von Stage-Re-Renders. Ich bin von 200ms/Frame auf 16ms/Frame gekommen. Hier ist der vollständige Guide.

Warum React Konva?

HTML5 Canvas ist schnell, aber imperativ – man schreibt Zeichenbefehle direkt. React ist deklarativ – man beschreibt, was gerendert werden soll. React Konva überbrückt beides: Man schreibt React-Komponenten, Konva übernimmt das Canvas-Zeichnen.

Für die meisten React-Apps ist das die richtige Wahl. Für Game-Engines, Video-Editoren oder Pixel-level-Kontrolle ist die reine Canvas API oder WebGL besser.

Was React Konva kann

  • Shapes: Rect, Circle, Line, Text, Image, SVG, Group
  • Interaktionen: Drag-and-Drop, Klick, Hover, Touch
  • Transformationen: Skalieren, Rotieren, Scheren
  • Filter: Blur, Helligkeit, Kontrast, Schatten
  • Layers: Mehrere Rendering-Layer für Performance
  • Hit Detection: Klick-Erkennung auf komplexen Shapes

React Konva Setup

Installation

bash
npm install react-konva konva@latest

Erste Shape

tsx
import { Stage, Layer, Rect, Circle, Text } from 'react-konva';

function Whiteboard() {
  const [shapes, setShapes] = React.useState([
    { id: '1', type: 'rect', x: 50, y: 50, width: 100, height: 60, fill: '#22c55e' },
    { id: '2', type: 'circle', x: 200, y: 80, radius: 40, fill: '#3b82f6' },
  ]);

  return (
    <Stage
      width={window.innerWidth}
      height={window.innerHeight}
      style={{ background: '#f9fafb' }}
    >
      <Layer>
        {shapes.map((shape) => {
          if (shape.type === 'rect') {
            return (
              <Rect
                key={shape.id}
                x={shape.x}
                y={shape.y}
                width={shape.width}
                height={shape.height}
                fill={shape.fill}
                draggable
                onDragEnd={(e) => {
                  setShapes(shapes.map(s =>
                    s.id === shape.id
                      ? { ...s, x: e.target.x(), y: e.target.y() }
                      : s
                  ));
                }}
              />
            );
          }
          return (
            <Circle
              key={shape.id}
              x={shape.x}
              y={shape.y}
              radius={shape.radius}
              fill={shape.fill}
              draggable
            />
          );
        })}
      </Layer>
    </Stage>
  );
}

Das rendert eine Canvas mit 2 verschiebbaren Shapes. Ausprobieren – Ziehen funktioniert mit Maus und Touch.

Die 3 Performance-Engpässe (und Lösungen)

Nach dem Profiling mit dem Chrome DevTools Performance Panel habe ich 3 Engpässe identifiziert, die 95 % meiner Performance-Probleme verursacht haben.

Engpass 1: Stage-Re-Renders

Die <Stage>-Komponente wird neu gerendert, sobald sich irgendein State im Parent ändert. Mit 500 Shapes und einem Theme-Toggle im Parent wurde bei jedem Toggle die gesamte Canvas neu gerendert.

Lösung: Stage memoizieren und eine separate Komponente für die Canvas verwenden:

tsx
// Die komplette Stage nicht als Prop übergeben – das löst Re-Renders aus
// Stattdessen: Ref verwenden und direkt aktualisieren
const stageRef = React.useRef<Konva.Stage>(null);

// Wenn sich ein Shape ändert, den Konva-Node direkt aktualisieren
const handleShapeMove = (shapeId: string, x: number, y: number) => {
  const layer = stageRef.current?.findOne('Layer');
  const shape = layer?.findOne(`#${shapeId}`);
  if (shape) {
    shape.position({ x, y });
    layer?.batchDraw(); // Nur diesen Layer neu zeichnen, nicht die ganze Stage
  }
};

Besseres Pattern: Canvas in eine eigene Komponente mit eigenem State aufteilen:

tsx
function CanvasBoard({ shapes }) {
  // Diese Komponente wird nur neu gerendert, wenn sich `shapes` ändert
  // Theme/State-Änderungen im Parent beeinflussen sie nicht
  return (
    <Stage width={width} height={height}>
      <ShapeLayer shapes={shapes} />
    </Stage>
  );
}

function ParentComponent() {
  // Theme- und UI-State-Änderungen beeinflussen CanvasBoard nicht
  const [theme, setTheme] = React.useState('light');
  const [shapes, setShapes] = React.useState([]);
  return (
    <div>
      <ThemeToggle theme={theme} onChange={setTheme} />
      <CanvasBoard shapes={shapes} />
    </div>
  );
}

Engpass 2: Keine Layer-Trennung

Alle Shapes auf einem Layer bedeuten, dass sich alle Shapes bei jeder Änderung neu zeichnen. Mit 500 Shapes löst das Ändern eines einzigen 500 Draw-Calls aus.

Lösung: Mehrere Layers verwenden und wechselnde Inhalte isolieren:

tsx
<Stage width={width} height={height}>
  {/* Layer 1: Statische Hintergrund-Shapes – wird selten neu gezeichnet */}
  <Layer listening={false}>
    <Rect x={0} y={0} width={width} height={height} fill="#f9fafb" />
    <GridLines width={width} height={height} />
  </Layer>

  {/* Layer 2: Dynamische Shapes – wird beim Ziehen neu gezeichnet */}
  <Layer>
    {shapes.map((shape) => (
      <DraggableShape key={shape.id} shape={shape} />
    ))}
  </Layer>

  {/* Layer 3: UI-Elemente (Auswahlbox, Hilfslinien) – sehr häufige Neuzeichnungen */}
  <Layer>
    {selectionBox && <SelectionRect {...selectionBox} />}
  </Layer>
</Stage>

Nur der Layer mit den wechselnden Inhalten wird neu gezeichnet. Statische Layer verwenden listening={false} und verbrauchen nie Rendering-Zeit.

Engpass 3: Fehlendes Caching

Shapes, die sich nicht ändern (Bilder, komplexe Vektor-Shapes), sollten als Bilder gecacht werden.

Lösung: cache() auf komplexen Shapes aktivieren:

tsx
<Rect
  x={100}
  y={100}
  width={200}
  height={200}
  fill="#22c55e"
  shadowColor="black"
  shadowBlur={10}
  shadowOpacity={0.2}
  // Dieses Shape als Bild cachen – Konva rendert es nicht bei jedem Frame neu
  cache
/>

Bei einer komplexen Group mit mehreren Shapes den gesamten Group cachen:

tsx
<Group cache x={50} y={50}>
  <Rect width={200} height={150} fill="#22c55e" />
  <Text text="Label" y={160} />
  <Circle x={100} y={75} radius={20} fill="#fff" />
</Group>

Wichtig: Nur Shapes cachen, die sich nicht häufig ändern. Gecachte Shapes können nicht aktualisiert werden, ohne den Cache zu regenerieren, was CPU-Zeit kostet.

Performance-Benchmarks: Vorher und Nachher

SzenarioVor OptimierungNach OptimierungVerbesserung
50 Shapes, Ziehen18ms/Frame6ms/Frame67 % schneller
500 Shapes, statisch120ms/Frame8ms/Frame93 % schneller
500 Shapes, Zoom200ms/Frame16ms/Frame92 % schneller
50 Shapes, Echtzeit-Sync80ms/Frame18ms/Frame78 % schneller

Gemessen mit Chrome DevTools Performance Profiler, 2019 MacBook Pro, 4x CPU-Drosselung.

Zoom und Pan hinzufügen

Zoom und Pan sind essenziell für die Whiteboard-UX. Konva handhabt das mit einem Transformer und Stage-Skalierung:

tsx
import { Transformer } from 'react-konva';

function ZoomableCanvas({ shapes }) {
  const stageRef = React.useRef<Konva.Stage>(null);
  const [scale, setScale] = React.useState(1);

  const handleWheel = (e: Konva.KonvaEventObject<WheelEvent>) => {
    e.evt.preventDefault();
    const stage = stageRef.current;
    if (!stage) return;

    const oldScale = scale;
    const pointer = stage.getPointerPosition()!;
    const newScale = e.evt.deltaY < 0 ? oldScale * 1.1 : oldScale / 1.1;

    // In Richtung der Pointer-Position zoomen
    const mousePointTo = {
      x: (pointer.x - stage.x()) / oldScale,
      y: (pointer.y - stage.y()) / oldScale,
    };

    stage.scale({ x: newScale, y: newScale });
    stage.position({
      x: pointer.x - mousePointTo.x * newScale,
      y: pointer.y - mousePointTo.y * newScale,
    });
    setScale(newScale);
  };

  return (
    <Stage
      ref={stageRef}
      width={width}
      height={height}
      onWheel={handleWheel}
      draggable
      onDragEnd={() => {
        // Viewport-Position speichern, falls nötig
      }}
    >
      {/* layers */}
    </Stage>
  );
}

Häufige Fehler, die ich gemacht habe

Fehler 1: Keine TypeScript-Generics verwenden

Ohne TypeScript fließen Shape-Daten durch any-Typen. Generics hinzufügen:

tsx
interface Shape {
  id: string;
  x: number;
  y: number;
  type: 'rect' | 'circle' | 'text';
}

function Shape({ shape }: { shape: Shape }) {
  switch (shape.type) {
    case 'rect':
      return <Rect x={shape.x} y={shape.y} />;
    case 'circle':
      return <Circle x={shape.x} y={shape.y} />;
    default:
      return null;
  }
}

Fehler 2: batchDraw() vergessen

Nach dem direkten Aktualisieren eines Shapes über einen Ref layer.batchDraw() aufrufen, um das nächste Rendering einzuplanen. Ohne das erscheinen Änderungen nicht.

Fehler 3: Zu viele Event-Handler auf der Stage

onClick, onDragStart, onDragEnd auf jedem Shape zu platzieren ist teuer. Stattdessen Event-Delegation auf dem Layer verwenden:

tsx
<Layer
  onClick={(e) => {
    const target = e.target;
    if (target === stageRef.current) return; // Leerer Bereich angeklickt
    // Shape-Klick behandeln
    const shapeId = target.id();
    onShapeSelect(shapeId);
  }}
>

Weiterführende Lektüre: React Internationalization Guide 2026 · Performance Budgeting Saas · Paraglide Js Modern React I18N

Eine Canvas-fähige React-Umgebung aufsetzen

TanStack Ship bietet eine React-Umgebung, die für Canvas-basierte Anwendungen optimiert ist. In Kombination mit React Konva erhalten Sie ein performantes Whiteboard-Setup ohne Infrastruktur-Arbeit. TanStack Ship erkunden →

Wenn Sie 2026 etwas mit Canvas Graphics bauen, ist React Konva der Ausgangspunkt. Die Performance-Probleme sind real, aber die Lösungen sind gut dokumentiert.


Aktualisiert 2026-09-18. Benchmarks auf 2019 MacBook Pro, Chrome 126, 4x CPU-Drosselung. React Konva v6.3+, Konva v9.4+. Getestet mit 50–500 Shapes, Drag, Zoom und Yjs Echtzeit-Sync.