Der Hintergrund wird erst weich, wenn etwas darüberläuft
Er war es immer: 28 Bildpunkte Weichzeichnung über die ganze Detailseite, damit der Text darüber lesbar bleibt. Oben steht aber gar nichts darüber — dort ist das Bild das Beste, was die Seite zu bieten hat, und es lag als Farbschlieren da. Unscharf gehört es erst, wenn wirklich etwas darüber hinwegläuft: die Folgen, die Besetzung, die Medienangaben. Jetzt folgt die Weichzeichnung dem Bildlauf, über etwa siebzig Prozent einer Bildschirmhöhe und quadratisch statt geradlinig — die ersten Bildpunkte sollen nichts tun, sonst wäre „scharf, solange nichts darüberliegt" nach einem Fingerbreit schon dahin. Gerundet auf ganze Bildpunkte, weil jeder neue Wert eine bildschirmfüllende Fläche neu zeichnen lässt; über die ganze Strecke sind das 28 Male. Gemessen über 58 Bilder beim Hinunterrollen: 8,3 ms im Mittel, kein Bild über 9,2 ms. **Und die Deckung geht mit, das ist der unscheinbare Teil.** Eine Weichzeichnung verrührt die hellen Stellen mit den dunklen; ein scharfes Bild hat die weisse Stelle genau dort, wo die Zeile steht. Gemessen an der dunklen Vorlage von „Dark": vorher 4,07∶1, scharf bei derselben Deckung nur noch 2,44∶1. An einer hellen — Hubschrauber vor blauem Himmel — vorher 2,75∶1. Mit 0,28 scharf und 0,45 weich stehen beide wieder bei rund 4,0∶1, die helle sogar besser als zuvor. Der Zuhörer räumt sich selbst ab: die Detailseite wird nicht gemerkt, und für ungemerkte Ansichten ruft `einhaengen` kein Abbruchsignal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6dc7be9822
commit
3c7c0d8799
+18
-1
@@ -513,8 +513,25 @@ svg { width: 20px; height: 20px; display: block; flex: none; }
|
||||
.detail-hintergrund {
|
||||
position: fixed; inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* **Scharf, solange nichts darüberliegt.** Die Weichzeichnung steht nicht
|
||||
* mehr fest hier, sondern wächst mit dem Bildlauf — gerechnet in
|
||||
* `weichNachBildlauf()`. Ohne Skript bleibt sie bei 0; dann sieht man ein
|
||||
* scharfes Bild, und das ist kein Fehler, sondern genau der Zustand, in dem
|
||||
* die Seite aufgeht. */
|
||||
.detail-bild {
|
||||
position: absolute; inset: 0;
|
||||
background-size: cover; background-position: center top;
|
||||
opacity: .45; filter: blur(28px) saturate(1.1);
|
||||
/* **Scharf etwas blasser als weich, und das ist gemessen.** Eine
|
||||
* Weichzeichnung verrührt die hellen Stellen mit den dunklen; ein scharfes
|
||||
* Bild hat die weisse Stelle genau dort, wo die Zeile steht. Bei „Dark"
|
||||
* kam der kleinste Abstand der Eckdaten zum Hintergrund auf 4,07∶1, solange
|
||||
* weichgezeichnet wurde — scharf bei derselben Deckung nur noch auf 2,44∶1.
|
||||
* Bei 0,28 sind es wieder 4,02∶1: so lesbar wie vorher, nur eben scharf. */
|
||||
opacity: var(--det-deckung, .28);
|
||||
filter: blur(var(--det-weich, 0px)) saturate(1.1);
|
||||
/* Weiter hochgezogen als das Bild selbst: die Unschärfe würde sonst am
|
||||
Rand als harte Kante sichtbar. */
|
||||
transform: scale(1.1);
|
||||
|
||||
@@ -108,7 +108,19 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
||||
const d = document.createElement('div');
|
||||
d.className = 'detail-hintergrund';
|
||||
const adresse = bildAdresse(schluessel);
|
||||
if (adresse) d.style.backgroundImage = `url("${adresse}")`;
|
||||
if (!adresse) return d;
|
||||
|
||||
// Das Bild liegt in einem eigenen Kind: die Weichzeichnung gehört ihm
|
||||
// allein, und die Fläche darüber bleibt frei für alles, was später noch
|
||||
// darauf will.
|
||||
const bild = document.createElement('div');
|
||||
bild.className = 'detail-bild';
|
||||
bild.style.backgroundImage = `url("${adresse}")`;
|
||||
d.appendChild(bild);
|
||||
|
||||
// Gesetzt wird auf dem Rahmen, nicht auf dem Bild: die Werte erben nach
|
||||
// unten, und der Schleier hängt als `::after` am Rahmen.
|
||||
weichNachBildlauf(d);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -743,3 +755,80 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
||||
|
||||
return wurzel;
|
||||
}
|
||||
|
||||
/* ---------- Der Hintergrund wird erst weich, wenn etwas darüberläuft ---------- */
|
||||
|
||||
/** Stärkste Weichzeichnung in Bildpunkten — der Wert, der bisher immer galt. */
|
||||
const WEICH_HOECHSTENS = 28;
|
||||
|
||||
/** Über wie viel Bildlauf sie aufgebaut wird, mindestens. */
|
||||
const STRECKE_MINDESTENS = 320;
|
||||
|
||||
/** Wie kräftig das Bild steht — scharf blasser als weich. Warum: `.detail-bild`. */
|
||||
const DECKUNG_SCHARF = 0.28;
|
||||
const DECKUNG_WEICH = 0.45;
|
||||
|
||||
/** Lässt den Hintergrund mit dem Bildlauf unscharf werden.
|
||||
*
|
||||
* **Vorher war er immer unscharf** — 28 Bildpunkte über die ganze Seite,
|
||||
* damit Text darüber lesbar bleibt. Oben steht aber gar kein Text darüber:
|
||||
* dort liegt rechts neben dem Plakat freie Fläche, und das Bild ist das
|
||||
* Beste, was die Seite zu bieten hat. Weich gezeichnet gehört es erst, wenn
|
||||
* wirklich etwas darüberläuft — die Folgen, die Besetzung, die Medienangaben.
|
||||
*
|
||||
* **Die Strecke hängt am Fenster, nicht am Inhalt.** Eine Seite mit zwei
|
||||
* Folgen und eine mit zweiundzwanzig sollen sich gleich anfühlen; gemessen
|
||||
* wird deshalb in Bildschirmhöhen, nicht in Abschnitten.
|
||||
*
|
||||
* **Quadratisch, nicht geradlinig.** Geradlinig wäre nach einem Fingerbreit
|
||||
* schon sichtbar weich — und „scharf, solange nichts darüberliegt" wäre
|
||||
* dahin. So bleiben die ersten Bildpunkte wirkungslos und der Aufbau kommt
|
||||
* dann, wenn der Kopfbereich tatsächlich verschwindet.
|
||||
*
|
||||
* **Auf ganze Bildpunkte gerundet**, weil jeder neue Wert ein neues Bild
|
||||
* kostet: eine fest stehende, bildschirmfüllende Fläche neu weichzuzeichnen
|
||||
* ist das Teuerste, was diese Seite tut. 28 Stufen reichen fürs Auge.
|
||||
*/
|
||||
function weichNachBildlauf(ziel: HTMLElement): void {
|
||||
const abbruch = new AbortController();
|
||||
let angemeldet = false;
|
||||
let zuletzt = -1;
|
||||
|
||||
const rechnen = (): void => {
|
||||
angemeldet = false;
|
||||
// **Räumt sich selbst ab.** Die Detailseite wird nicht gemerkt; sie
|
||||
// verschwindet beim Seitenwechsel ersatzlos, und `einhaengen` ruft für
|
||||
// ungemerkte Ansichten kein Abbruchsignal. Hängt das Ziel nicht mehr am
|
||||
// Dokument, ist dieser Zuhörer zu Ende.
|
||||
if (!ziel.isConnected) { abbruch.abort(); return; }
|
||||
|
||||
const strecke = Math.max(STRECKE_MINDESTENS, window.innerHeight * 0.7);
|
||||
const anteil = Math.min(1, Math.max(0, window.scrollY / strecke));
|
||||
const weich = Math.round(anteil * anteil * WEICH_HOECHSTENS);
|
||||
if (weich === zuletzt) return;
|
||||
zuletzt = weich;
|
||||
ziel.style.setProperty('--det-weich', `${weich}px`);
|
||||
// Die Deckung geht denselben Weg mit — siehe `.detail-bild` im
|
||||
// Regelwerk: scharf darf das Bild nicht so kräftig stehen wie weich,
|
||||
// sonst steht die Zeile darüber auf einer weissen Stelle.
|
||||
const deckung = DECKUNG_SCHARF
|
||||
+ (DECKUNG_WEICH - DECKUNG_SCHARF) * (weich / WEICH_HOECHSTENS);
|
||||
ziel.style.setProperty('--det-deckung', deckung.toFixed(3));
|
||||
};
|
||||
|
||||
const anstossen = (): void => {
|
||||
if (angemeldet) return;
|
||||
angemeldet = true;
|
||||
requestAnimationFrame(rechnen);
|
||||
};
|
||||
|
||||
const wie = { passive: true, signal: abbruch.signal } as const;
|
||||
window.addEventListener('scroll', anstossen, wie);
|
||||
window.addEventListener('resize', anstossen, wie);
|
||||
// **Einmal anstossen statt sofort rechnen.** Gerechnet wird hier noch zu
|
||||
// früh: `hintergrund()` läuft, bevor die Ansicht am Dokument hängt und
|
||||
// bevor `einhaengen` den Bildlauf setzt — gemessen würde die Stelle der
|
||||
// *vorigen* Seite. Ein Bild später stimmt beides, und weil die Rückfrage
|
||||
// vor dem ersten Zeichnen drankommt, sieht man dazwischen nichts.
|
||||
anstossen();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user