Öffentliche Avatar-Uploads vor dem Absenden verkleinern (1,1 MB -> ~40 KB)
Der Performance-Check (pruef-tempo.mjs, neu) fand als größte Einzel- ressource ein 1,1-MB-Profilbild, ausgeliefert an jeden Besucher der Startseite und von stimmen.html -- angezeigt wird es handgroß. Ursache: Das öffentliche Stimmen-Formular (stimmen.js) lud Profilbilder ROH hoch. Die Bildaufbereitung vom 27.08. bekam nur die Verwaltung, nicht das öffentliche Formular. So landete das Kamera-Foto in voller Auflösung auf dem Server. - bild-vorbereiten.js (neu): die Verkleinerungs-Funktion, jetzt einmal und parametrisierbar (maxBreite). Verwaltung nutzt weiter 1600px, Avatare 512px. Test pruef-bild-vorbereiten.mjs: 8/8, u.a. 512er-Avatar ~40 KB statt >1 MB. - stimmen.js: verkleinert vor dem Upload (window.bildVorbereiten mit maxBreite 512); fällt die Funktion aus, wird das Original genommen -- kein Upload darf daran scheitern. - stimmen.html: lädt bild-vorbereiten.js; veralteten Kommentar richtiggestellt (behauptete "kein Upload", obwohl es seit 21.08. einen gibt -- mit Bremse, Typ-/Größenlimit, Freigabe-Pflicht). Zwei neue Checks als bleibende Absicherung mit committet: pruef-links.mjs (41 interne Ziele, 0 kaputt) und pruef-assets.mjs (67 Ressourcen, 0 fehlen). NOCH OFFEN: verwaltung.html hat noch eine eigene, identische Inline-Kopie der Funktion -- die Zusammenführung ist ein eigener, testbarer Schritt (das Inline-Skript dort ist groß und die Verwaltung hinter dem Gate schwer live zu testen). Das bestehende 1,1-MB-Bild auf dem Server bleibt, bis es neu hochgeladen/ersetzt wird. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/* =====================================================================
|
||||
Verkleinert bild-vorbereiten.js richtig?
|
||||
|
||||
Läuft in einem echten Browser (Canvas/Image gibt es nur dort). Erzeugt
|
||||
Testbilder verschiedener Größe/Format und prüft das Verhalten, auf das
|
||||
es ankommt: große Bilder werden auf die vorgegebene Breite gebracht,
|
||||
kleine bleiben unangetastet, große PNGs werden zu JPEG, und die
|
||||
Avatar-Grenze (512) macht ein Foto deutlich kleiner als die
|
||||
Verwaltungs-Grenze (1600).
|
||||
===================================================================== */
|
||||
import { chromium } from "playwright";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const quelle = readFileSync("assets/js/bild-vorbereiten.js", "utf8");
|
||||
const browser = await chromium.launch();
|
||||
const seite = await browser.newPage();
|
||||
await seite.setContent("<!doctype html><html><body></body></html>");
|
||||
await seite.addScriptTag({ content: quelle });
|
||||
|
||||
let ok = 0, fehl = 0;
|
||||
const pruefe = (name, gut, info) => {
|
||||
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
|
||||
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); }
|
||||
};
|
||||
|
||||
/* Hilfsfunktion im Browser: erzeugt ein Testbild als File, jagt es durch
|
||||
bildVorbereiten und meldet Maße/Größe/Typ vorher und nachher. */
|
||||
const test = (breite, hoehe, typ, opt) => seite.evaluate(async ([breite, hoehe, typ, opt]) => {
|
||||
const c = document.createElement("canvas");
|
||||
c.width = breite; c.height = hoehe;
|
||||
const ctx = c.getContext("2d");
|
||||
/* Echtes Zufallsrauschen, damit die Testbilder realistisch groß werden
|
||||
(ein Muster oder eine Fläche würde PNG so stark komprimieren, dass die
|
||||
"großes PNG"-Grenze von 400 KB nie erreicht wird -- dann bliebe der
|
||||
PNG->JPEG-Pfad ungetestet). */
|
||||
const img = ctx.createImageData(breite, hoehe);
|
||||
for (let i = 0; i < img.data.length; i += 4) {
|
||||
img.data[i] = Math.random() * 256; img.data[i+1] = Math.random() * 256;
|
||||
img.data[i+2] = Math.random() * 256; img.data[i+3] = 255;
|
||||
}
|
||||
ctx.putImageData(img, 0, 0);
|
||||
const blob = await new Promise((f) => c.toBlob(f, typ, 0.95));
|
||||
const datei = new File([blob], "test." + (typ === "image/png" ? "png" : "jpg"), { type: typ });
|
||||
|
||||
const raus = await window.bildVorbereiten(datei, opt);
|
||||
/* Maße des Ergebnisses auslesen. */
|
||||
const masse = await new Promise((f) => {
|
||||
const b = new Image();
|
||||
b.onload = () => f({ w: b.naturalWidth, h: b.naturalHeight });
|
||||
b.src = URL.createObjectURL(raus);
|
||||
});
|
||||
return { vorherBytes: datei.size, nachherBytes: raus.size, w: masse.w, h: masse.h, typ: raus.type };
|
||||
}, [breite, hoehe, typ, opt]);
|
||||
|
||||
console.log("\n=== Großes Foto, Verwaltungs-Grenze (1600) ===");
|
||||
{
|
||||
const r = await test(2400, 1350, "image/jpeg", { maxBreite: 1600 });
|
||||
pruefe("wird auf 1600 Breite gebracht", r.w === 1600, `${r.w}px breit`);
|
||||
pruefe("wird kleiner", r.nachherBytes < r.vorherBytes, `${Math.round(r.vorherBytes/1024)} -> ${Math.round(r.nachherBytes/1024)} KB`);
|
||||
}
|
||||
|
||||
console.log("\n=== Großes Foto, Avatar-Grenze (512) ===");
|
||||
{
|
||||
const r = await test(2400, 1350, "image/jpeg", { maxBreite: 512 });
|
||||
pruefe("wird auf 512 Breite gebracht", r.w === 512, `${r.w}px breit`);
|
||||
pruefe("Avatar ist DEUTLICH kleiner als 1600er", r.nachherBytes < 80 * 1024, `${Math.round(r.nachherBytes/1024)} KB`);
|
||||
}
|
||||
|
||||
console.log("\n=== Kleines Bild bleibt unangetastet ===");
|
||||
{
|
||||
const r = await test(300, 300, "image/jpeg", { maxBreite: 512 });
|
||||
pruefe("300px-Bild behält 300px", r.w === 300, `${r.w}px`);
|
||||
}
|
||||
|
||||
console.log("\n=== Großes PNG wird zu JPEG ===");
|
||||
{
|
||||
const r = await test(2000, 1200, "image/png", { maxBreite: 1600 });
|
||||
pruefe("Format wird JPEG", r.typ === "image/jpeg", r.typ);
|
||||
pruefe("auf 1600 gebracht", r.w === 1600, `${r.w}px`);
|
||||
}
|
||||
|
||||
console.log("\n=== Seitenverhältnis bleibt erhalten ===");
|
||||
{
|
||||
const r = await test(1600, 900, "image/jpeg", { maxBreite: 800 });
|
||||
const verh = (r.w / r.h).toFixed(2);
|
||||
pruefe("16:9 bleibt 16:9", verh === "1.78", `${r.w}x${r.h} (${verh})`);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
|
||||
process.exitCode = fehl ? 1 : 0;
|
||||
Reference in New Issue
Block a user