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]>
114 lines
4.8 KiB
JavaScript
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;
|
|
})();
|