Files
dogfather-universe/assets/js/bild-vorbereiten.js
T
DogFatherGitandClaude Opus 5 5b2a1daabe Ö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]>
2026-08-27 15:17:30 +02:00

114 lines
4.8 KiB
JavaScript

/* =====================================================================
bild-vorbereiten.js — ein Bild im Browser verkleinern, BEVOR es hochlädt
WOZU
Serverseitig bräuchte es eine Bildbibliothek (sharp o.ä.) -- eine neue
Abhängigkeit mit nativem Code, installiert in einem Verzeichnis, an das
Claudian nicht herankommt. Der Browser kann das ohnehin: Canvas skaliert
und kodiert seit jeher. Nebeneffekt: schon der Upload wird kleiner -- wer
vom Handy ein 8-MB-Foto schickt, wartet sonst am Mobilfunknetz.
Bis 28.08.2026 lag diese Funktion NUR inline in der Verwaltung
(verwaltung.html). Das öffentliche Stimmen-Formular (stimmen.js) lud
Profilbilder dagegen ROH hoch -- so landete ein 1,1-MB-Avatar auf dem
Server und wurde jedem Besucher der Startseite ausgeliefert. Deshalb
liegt die Funktion jetzt hier, EINMAL, und wird von beiden benutzt.
MASS PRO EINSATZORT
Die größte Breite ist ein Parameter, kein fester Wert: Die Verwaltung
zeigt Event-/Teambilder groß (1600 reicht für jede Anzeige dort), ein
Profil-Avatar erscheint dagegen nur handgroß -- 512 ist da schon
großzügig und spart gegenüber 1600 ein Vielfaches.
WAS BEWUSST NICHT ANGETASTET WIRD
Kleine PNGs bleiben PNG. Ein Logo mit durchsichtigem Hintergrund würde
als JPEG einen schwarzen Kasten bekommen -- JPEG kennt keine Transparenz.
Die Grenze liegt bei 400 KB: Darunter ist es wahrscheinlich eine Grafik,
darüber praktisch immer ein Foto.
===================================================================== */
(function () {
const STANDARD = {
maxBreite: 1600, // reicht für die großen Anzeigen in der Verwaltung
qualitaet: 0.86,
pngGrenze: 400 * 1024, // kleine PNGs (Grafiken/Logos) unangetastet lassen
};
/**
* @param {File} datei die vom Nutzer gewählte Datei
* @param {object} [opt] {maxBreite, qualitaet, pngGrenze}
* @returns {Promise<File>} verkleinerte Datei -- oder das Original, wenn
* sich Verkleinern nicht lohnt oder etwas schiefgeht.
*/
async function bildVorbereiten(datei, opt) {
const maxBreite = opt?.maxBreite ?? STANDARD.maxBreite;
const qualitaet = opt?.qualitaet ?? STANDARD.qualitaet;
const pngGrenze = opt?.pngGrenze ?? STANDARD.pngGrenze;
try {
if (!datei || !datei.type.startsWith("image/")) return datei;
const istPng = datei.type === "image/png";
const bild = await new Promise((fertig, schief) => {
const b = new Image();
b.onload = () => fertig(b);
b.onerror = schief;
b.src = URL.createObjectURL(datei);
});
const zuBreit = bild.naturalWidth > maxBreite;
const grossesPng = istPng && datei.size > pngGrenze;
/* Nichts zu tun: kleines Bild, richtiges Format. Dann bleibt die
Datei unverändert -- kein Qualitätsverlust ohne Gegenwert. */
if (!zuBreit && !grossesPng) {
URL.revokeObjectURL(bild.src);
return datei;
}
const breite = Math.min(bild.naturalWidth, maxBreite);
const hoehe = Math.round((bild.naturalHeight / bild.naturalWidth) * breite);
const flaeche = document.createElement("canvas");
flaeche.width = breite;
flaeche.height = hoehe;
const stift = flaeche.getContext("2d");
/* Ohne diese beiden Zeilen skaliert der Browser hart und die Kanten
fransen aus. */
stift.imageSmoothingEnabled = true;
stift.imageSmoothingQuality = "high";
/* Große PNGs werden zu JPEG -- die haben keinen durchsichtigen
Hintergrund, sonst wären sie nicht so groß. Sicherheitshalber wird
trotzdem weiß unterlegt, damit aus einer eventuellen Transparenz
kein schwarzer Kasten wird. */
if (grossesPng) {
stift.fillStyle = "#ffffff";
stift.fillRect(0, 0, breite, hoehe);
}
stift.drawImage(bild, 0, 0, breite, hoehe);
URL.revokeObjectURL(bild.src);
const typ = (grossesPng || datei.type === "image/jpeg") ? "image/jpeg" : datei.type;
const klotz = await new Promise((fertig) =>
flaeche.toBlob(fertig, typ, qualitaet));
/* Wurde es NICHT kleiner, bleibt das Original. Bei bereits gut
komprimierten Dateien kann erneutes Kodieren sonst sogar zulegen --
und Qualität kosten für nichts. */
if (!klotz || klotz.size >= datei.size) return datei;
const name = datei.name.replace(/\.[^.]+$/, "") +
(typ === "image/jpeg" ? ".jpg" : datei.name.slice(datei.name.lastIndexOf(".")));
return new File([klotz], name, { type: typ, lastModified: Date.now() });
} catch (e) {
/* Geht irgendetwas schief, wird das Original hochgeladen. Ein
fehlgeschlagenes Verkleinern darf niemals einen Upload verhindern. */
return datei;
}
}
window.bildVorbereiten = bildVorbereiten;
})();