Heatmap-Demo: ein Klick = ein Punkt an der Zeigerstelle, weich eingeblendet
Wunsch des Inhabers: ein simulierter Klick soll nicht mehrere Heatpunkte auf einmal bringen. Die Heatmap der letzten 30 Tage ist jetzt von Anfang an ganz zu sehen; jeder Demo-Klick legt genau einen Punkt an die Stelle des Zeigers, der in gut 0,4 s weich hereinwächst. Beim Neustart blenden nur die Demo-Klicks aus, nicht die ganze Heatmap. heat.ts: heatEbene() zeichnet den Grundbestand einmal und färbt je Bild nur den kleinen Ausschnitt um die sich ändernden Punkte neu ein (gemessen ca. 2.000 Pixel je Klick), nach der Überblendung läuft nichts mehr. Ohne Bewegung: alles sofort im Endzustand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
adb1587a7b
commit
58ac7745e6
4 files changed
+179
-59
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type MouseEvent, type RefObject } from "react";
|
||||
import { DemoZeiger, useFeinerZeiger, zufall, type DemoVariante } from "../gemeinsam";
|
||||
import { punkteInZone, zeichneHeatmap, type HeatPunkt } from "../heat";
|
||||
import { HEATMAP_START, type HeatmapZustand } from "./ablauf";
|
||||
import { DemoZeiger, useBewegungErlaubt, useFeinerZeiger, zufall, type DemoVariante } from "../gemeinsam";
|
||||
import { heatEbene, punkteInZone, type HeatPunkt, type KlickPunkt } from "../heat";
|
||||
import { HEATMAP_START, KLICK_ZIELE, type HeatmapZustand } from "./ablauf";
|
||||
import { FLAECHE_GROESSE, TOP_ZONE, ZONEN, ZONEN_LAYOUT, schildText, zoneNach, type Zone, type PunktRechteck } from "./zonen";
|
||||
import "./heatmap.css";
|
||||
|
||||
@@ -382,20 +382,56 @@ const HeatmapSeite = ({ zustand = HEATMAP_START, variante }: Props) => {
|
||||
const rechtecke = useGemessen(wurzel, variante);
|
||||
const punkte = useMemo(() => erzeugePunkte(rechtecke, variante), [rechtecke, variante]);
|
||||
const { fuellung, statisch } = zustand;
|
||||
const bewegung = useBewegungErlaubt();
|
||||
const ebene = useRef<ReturnType<typeof heatEbene> | null>(null);
|
||||
/** Stärke jedes Demo-Klicks, wie sie gerade gezeichnet ist (0 bis 1) */
|
||||
const staerken = useRef<number[]>([]);
|
||||
|
||||
// Neu zeichnen nur, wenn sich die sichtbaren Punkte ändern (neuer Klick, neue Messung), nicht je Bild
|
||||
// Grundbestand (Klicks der letzten 30 Tage): neu zeichnen nur bei neuer Messung, nicht je Bild
|
||||
useEffect(() => {
|
||||
const c = leinwand.current;
|
||||
if (!c) return;
|
||||
const sichtbar: HeatPunkt[] = [];
|
||||
ebene.current = heatEbene(c, LEINWAND[variante].radius);
|
||||
const grund: HeatPunkt[] = [];
|
||||
for (const z of ZONEN) {
|
||||
const alle = punkte[z.id];
|
||||
const anzahl = Math.round((fuellung[z.id] ?? 0) * alle.length);
|
||||
if (anzahl > 0) sichtbar.push(...alle.slice(0, anzahl));
|
||||
grund.push(...alle.slice(0, Math.round((fuellung[z.id] ?? 0) * alle.length)));
|
||||
}
|
||||
zeichneHeatmap(c, sichtbar, LEINWAND[variante].radius);
|
||||
ebene.current.basis(grund);
|
||||
staerken.current = [];
|
||||
}, [punkte, fuellung, variante]);
|
||||
|
||||
// Demo-Klicks: jeder Klick ist genau EIN Punkt an der Stelle des Zeigers. Ein neuer Punkt wächst in gut 0,4 s weich
|
||||
// herein, beim Neustart blenden alle Demo-Klicks ebenso weich aus. Neu eingefärbt wird je Bild nur der kleine
|
||||
// Ausschnitt um die Punkte, die sich ändern; nach der Überblendung läuft nichts mehr.
|
||||
const gesetzt = zustand.klickPunkte[variante].length;
|
||||
useEffect(() => {
|
||||
const e = ebene.current;
|
||||
if (!e) return;
|
||||
const { breite, hoehe } = FLAECHE_GROESSE[variante];
|
||||
const lage = KLICK_ZIELE[variante].map((p) => ({ x: p.x / breite, y: p.y / hoehe }));
|
||||
const von = lage.map((_, i) => staerken.current[i] ?? 0);
|
||||
const nach = lage.map((_, i) => (i < gesetzt ? 1 : 0));
|
||||
const setze = (t: number) => {
|
||||
staerken.current = von.map((v, i) => v + (nach[i] - v) * t);
|
||||
e.klicks(lage.map((p, i): KlickPunkt => ({ ...p, staerke: staerken.current[i] })));
|
||||
};
|
||||
if (!bewegung || von.every((v, i) => v === nach[i])) {
|
||||
setze(1);
|
||||
return;
|
||||
}
|
||||
const dauer = nach.some((n, i) => n < von[i]) ? 700 : 420;
|
||||
let raf = 0;
|
||||
const beginn = performance.now();
|
||||
const bild = (jetzt: number) => {
|
||||
const t = Math.min(1, (jetzt - beginn) / dauer);
|
||||
setze(1 - (1 - t) ** 3); // sanft abbremsend
|
||||
if (t < 1) raf = requestAnimationFrame(bild);
|
||||
};
|
||||
raf = requestAnimationFrame(bild);
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}, [gesetzt, variante, bewegung, punkte, fuellung]);
|
||||
|
||||
const zoneUnter = (e: MouseEvent) => {
|
||||
const z = (e.target as Element).closest?.("[data-zone]");
|
||||
return z?.getAttribute("data-zone") ?? null;
|
||||
|
||||
Reference in new issue
Block a user