Bilder bewegen sich unter dem Zeiger
Faehrt der Zeiger ueber ein Bild, tritt es leicht naeher und wandert ein Stueck GEGEN die Zeigerrichtung -- als schaue man durch ein Fenster und lehne sich zur Seite. Warum gegen die Richtung: Bewegt sich das Bild MIT dem Zeiger, wirkt es wie ein Aufkleber, der verrutscht. Nur die Gegenbewegung liest das Auge als Tiefe hinter dem Rahmen. Derselbe Grund wie bei der Buehne im Verwaltungsbereich, eine Ebene kleiner. Der Effekt liegt im GRUNDSYSTEM, nicht in einer einzelnen Seite. Er gilt damit ueberall: Portfolio, Startseite, "Ueber mich", Zugangswand und die Vorschaubilder im Verwaltungsbereich. Eine Sonderloesung je Seite waere beim naechsten neuen Bild wieder vergessen worden. Zwei Zahlen, die bewusst klein sind: - Der Ausschlag liegt bei hoechstens 14 Punkten (gemessen 13,9). - Der Zoom bei 1,055. Zusammen ergibt das Bewegung, ohne dass ein Bild beim blossen Vorbeifahren seinen Ausschnitt merklich aendert. Ein groesserer Wert waere kein Effekt mehr, sondern ein Bildsprung. Umgerechnet wird auf die Groesse des jeweiligen Bildes (-1 bis +1), nicht in festen Bildpunkten. Sonst wanderten ein Vorschaubild von 1200 Punkten Breite und ein Logo von 80 gleich weit -- beim kleinen saehe das aus wie ein Ruck. Zwei Faelle, die im Code ausdruecklich abgefangen sind: - Der Zeiger liegt ueber einem Element, das das Bild UEBERDECKT (etwa einem Textblock in derselben Kachel). Ohne Behandlung bliebe das Bild stehen, sobald man den Rahmen verlaesst, aber die Kachel noch nicht. - Der Zeiger steht neben dem Bild, aber noch in der Kachel. Die Werte liefen dann weit ueber 1 hinaus und das Bild schoesse aus dem Rahmen. Deshalb wird auf -1 bis +1 begrenzt. Beim Verlassen stellt sich alles zurueck -- sonst bliebe das Bild verschoben stehen, nachdem der Zeiger laengst weg ist. Bei prefers-reduced-motion ist der Effekt aus. Geprueft: 265 Pruefungen gruen (Portfolio 20, Verwaltung 62, CRYONOVA 53, Blickfang 13, System 5, Abmelden 16, Abbruch 42, Angebot 54). Der Test prueft ausdruecklich die RICHTUNG der Bewegung, nicht nur, dass sich ueberhaupt etwas tut. Co-Authored-By: Claude Opus 5 <[email protected]>
@@ -624,6 +624,62 @@ a.wd-karte:hover, .wd-karte--klickbar:hover {
|
||||
.wd-tag--indigo { background: rgb(var(--wd-indigo-rgb) / .16); border-color: rgb(var(--wd-indigo-rgb) / .40); color: rgb(var(--wd-chrome-rgb)); }
|
||||
.wd-tags { display: flex; flex-wrap: wrap; gap: .45rem; }
|
||||
|
||||
/* =====================================================================
|
||||
BILDER, DIE SICH BEWEGEN
|
||||
|
||||
Faehrt der Zeiger ueber ein Bild, tritt es leicht naeher und wandert
|
||||
ein Stueck gegen die Zeigerrichtung -- als schaue man durch ein
|
||||
Fenster und lehne sich zur Seite.
|
||||
|
||||
Warum GEGEN die Richtung: Bewegt sich das Bild mit dem Zeiger, wirkt
|
||||
es wie ein Aufkleber, der verrutscht. Bewegt es sich dagegen, liest
|
||||
das Auge es als Tiefe hinter dem Rahmen. Derselbe Grund wie bei der
|
||||
Buehne im Verwaltungsbereich.
|
||||
|
||||
Der Ausschlag ist klein (hoechstens 14 Punkte) und der Zoom knapp
|
||||
ueber eins. Beides zusammen ergibt Bewegung, ohne dass ein Bild beim
|
||||
Ueberfahren seinen Ausschnitt merklich aendert -- sonst waere jedes
|
||||
Vorbeifahren mit der Maus ein Bildsprung.
|
||||
===================================================================== */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
|
||||
/* Der Rahmen muss den Ueberstand abschneiden, sonst schiebt sich das
|
||||
vergroesserte Bild ueber die Kachelkante hinaus. */
|
||||
.wd-bildrahmen,
|
||||
.pfo-projekt,
|
||||
.wd-karte:has(> img:first-child) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pfo-bild,
|
||||
.wd-projekt-bild,
|
||||
.wd-bildrahmen > img,
|
||||
.wd-karte > img {
|
||||
transform:
|
||||
scale(var(--wd-bildzoom, 1))
|
||||
translate3d(calc(var(--wd-bx, 0) * -14px), calc(var(--wd-by, 0) * -10px), 0);
|
||||
transition: transform .55s cubic-bezier(.2,.75,.25,1);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* Der Zoom kommt erst beim Ueberfahren dazu. Als Dauerzustand waere
|
||||
jedes Bild permanent leicht beschnitten. */
|
||||
.pfo-projekt:hover .pfo-bild,
|
||||
.wd-bildrahmen:hover > img,
|
||||
.wd-karte:hover > img,
|
||||
.pfo-bild:hover,
|
||||
.wd-projekt-bild:hover {
|
||||
--wd-bildzoom: 1.055;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.pfo-bild, .wd-projekt-bild, .wd-bildrahmen > img, .wd-karte > img {
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
7. PREISKARTEN (Masterplan S.8)
|
||||
Preis, Dauer und Personalisierungshinweis gehören sichtbar zusammen —
|
||||
|
||||
@@ -946,6 +946,72 @@
|
||||
k.style.setProperty("--wd-lichty", (letzte.y - r.top) + "px");
|
||||
});
|
||||
}, { passive: true });
|
||||
|
||||
bilderBewegen();
|
||||
}
|
||||
|
||||
/* ===================================================================
|
||||
BILDER, DIE SICH BEWEGEN
|
||||
|
||||
Faehrt der Zeiger ueber ein Bild, wandert es ein Stueck GEGEN die
|
||||
Zeigerrichtung. Bewegte es sich mit, wirkte es wie ein Aufkleber,
|
||||
der verrutscht; dagegen liest das Auge es als Tiefe hinter dem
|
||||
Rahmen.
|
||||
|
||||
Gesetzt werden Werte von -1 bis +1, umgerechnet auf die Groesse des
|
||||
Bildes. Ein fester Wert in Bildpunkten waere falsch: Ein
|
||||
Vorschaubild von 1200 Punkten Breite und ein Kundenlogo von 80
|
||||
wuerden sonst gleich weit wandern, was beim kleinen wie ein Ruck
|
||||
aussieht.
|
||||
=================================================================== */
|
||||
function bilderBewegen() {
|
||||
var WAHL = ".pfo-bild, .wd-projekt-bild, .wd-bildrahmen > img, .wd-karte > img";
|
||||
var offen = false, letztes = null, zx = 0, zy = 0;
|
||||
|
||||
document.addEventListener("pointermove", function (e) {
|
||||
var bild = null;
|
||||
if (e.target && e.target.matches && e.target.matches(WAHL)) {
|
||||
bild = e.target;
|
||||
} else if (e.target && e.target.closest) {
|
||||
/* Auch dann greifen, wenn der Zeiger ueber einem Element LIEGT,
|
||||
das das Bild ueberdeckt -- etwa einem Textblock in derselben
|
||||
Kachel. Sonst bliebe das Bild stehen, sobald man den Rahmen
|
||||
verlaesst, aber die Kachel noch nicht. */
|
||||
var rahmen = e.target.closest(".wd-bildrahmen, .pfo-projekt, .wd-karte");
|
||||
if (rahmen) bild = rahmen.querySelector(WAHL);
|
||||
}
|
||||
if (!bild) return;
|
||||
letztes = bild; zx = e.clientX; zy = e.clientY;
|
||||
if (offen) return;
|
||||
offen = true;
|
||||
requestAnimationFrame(function () {
|
||||
offen = false;
|
||||
if (!letztes) return;
|
||||
var r = letztes.getBoundingClientRect();
|
||||
if (!r.width || !r.height) return;
|
||||
var bx = ((zx - r.left) / r.width - 0.5) * 2;
|
||||
var by = ((zy - r.top) / r.height - 0.5) * 2;
|
||||
/* Auf -1..+1 begrenzen: Steht der Zeiger neben dem Bild, aber
|
||||
noch in der Kachel, kaemen sonst Werte weit darueber heraus
|
||||
und das Bild schoesse aus dem Rahmen. */
|
||||
letztes.style.setProperty("--wd-bx", Math.max(-1, Math.min(1, bx)).toFixed(3));
|
||||
letztes.style.setProperty("--wd-by", Math.max(-1, Math.min(1, by)).toFixed(3));
|
||||
});
|
||||
}, { passive: true });
|
||||
|
||||
/* Beim Verlassen zuruecksetzen, sonst bleibt das Bild verschoben
|
||||
stehen, nachdem der Zeiger laengst weg ist. */
|
||||
document.addEventListener("pointerout", function (e) {
|
||||
if (!e.target || !e.target.closest) return;
|
||||
var rahmen = e.target.closest(".wd-bildrahmen, .pfo-projekt, .wd-karte");
|
||||
if (!rahmen) return;
|
||||
if (e.relatedTarget && rahmen.contains(e.relatedTarget)) return;
|
||||
var bild = rahmen.querySelector(WAHL);
|
||||
if (!bild) return;
|
||||
bild.style.setProperty("--wd-bx", "0");
|
||||
bild.style.setProperty("--wd-by", "0");
|
||||
if (letztes === bild) letztes = null;
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
/* Nach außen sichtbare Schnittstelle für seiteneigene Skripte. */
|
||||
|
||||
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 302 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 4.3 MiB After Width: | Height: | Size: 4.1 MiB |
|
Before Width: | Height: | Size: 578 KiB After Width: | Height: | Size: 578 KiB |
@@ -189,7 +189,58 @@ srv.listen(4403, async () => {
|
||||
pruefe(`Schild "${d.text}" ist gut lesbar (AA = 4,5:1)`, v >= 4.5, v.toFixed(2) + ":1");
|
||||
}
|
||||
|
||||
console.log("\n5. KEINE FEHLER, KEINE FEHLENDEN DATEIEN");
|
||||
console.log("\n5. BEWEGEN SICH DIE BILDER?");
|
||||
const bild = seite.locator(".pfo-bild").first();
|
||||
await bild.scrollIntoViewIfNeeded();
|
||||
await seite.waitForTimeout(800);
|
||||
const rahmen = await bild.boundingBox();
|
||||
|
||||
/* Mit steps bewegen, sonst entsteht kein pointermove. */
|
||||
await seite.mouse.move(rahmen.x + 30, rahmen.y + 30, { steps: 10 });
|
||||
await seite.waitForTimeout(600);
|
||||
const linksOben = await bild.evaluate((e) => ({
|
||||
bx: parseFloat(e.style.getPropertyValue("--wd-bx")),
|
||||
versatz: getComputedStyle(e).transform,
|
||||
}));
|
||||
await seite.mouse.move(rahmen.x + rahmen.width - 30,
|
||||
rahmen.y + rahmen.height - 30, { steps: 18 });
|
||||
await seite.waitForTimeout(650);
|
||||
const rechtsUnten = await bild.evaluate((e) => ({
|
||||
bx: parseFloat(e.style.getPropertyValue("--wd-bx")),
|
||||
versatz: getComputedStyle(e).transform,
|
||||
}));
|
||||
|
||||
pruefe("das Bild reagiert auf den Zeiger",
|
||||
linksOben.bx < -0.4 && rechtsUnten.bx > 0.4,
|
||||
linksOben.bx + " -> " + rechtsUnten.bx);
|
||||
|
||||
/* Die Richtung ist der Kern: Bewegt sich das Bild MIT dem Zeiger,
|
||||
wirkt es wie ein verrutschter Aufkleber. Nur die Gegenbewegung
|
||||
liest das Auge als Tiefe hinter dem Rahmen. */
|
||||
const holeX = (m) => parseFloat(m.slice(m.indexOf("(") + 1, -1).split(",")[4]);
|
||||
const xLinks = holeX(linksOben.versatz);
|
||||
const xRechts = holeX(rechtsUnten.versatz);
|
||||
pruefe("und wandert GEGEN die Zeigerrichtung",
|
||||
xLinks > 0 && xRechts < 0,
|
||||
xLinks.toFixed(1) + "px / " + xRechts.toFixed(1) + "px");
|
||||
|
||||
/* Klein halten: Ein grosser Ausschnittswechsel beim blossen
|
||||
Vorbeifahren waere ein Bildsprung, kein Effekt. */
|
||||
pruefe("der Ausschlag bleibt klein",
|
||||
Math.abs(xLinks) <= 18 && Math.abs(xRechts) <= 18,
|
||||
Math.max(Math.abs(xLinks), Math.abs(xRechts)).toFixed(1) + "px");
|
||||
|
||||
const zoom = parseFloat(rechtsUnten.versatz.slice(
|
||||
rechtsUnten.versatz.indexOf("(") + 1, -1).split(",")[0]);
|
||||
pruefe("und das Bild tritt leicht naeher", zoom > 1.01 && zoom < 1.12,
|
||||
zoom.toFixed(3) + "-fach");
|
||||
|
||||
await seite.mouse.move(10, 10, { steps: 12 });
|
||||
await seite.waitForTimeout(700);
|
||||
const zurueck = await bild.evaluate((e) => e.style.getPropertyValue("--wd-bx"));
|
||||
pruefe("beim Verlassen stellt es sich zurueck", zurueck === "0", zurueck);
|
||||
|
||||
console.log("\n6. KEINE FEHLER, KEINE FEHLENDEN DATEIEN");
|
||||
pruefe("kein Skriptfehler", skriptfehler.length === 0, skriptfehler[0] || "");
|
||||
const verpasst = fehlt.filter((f) => /\.(jpg|webp|png|css|js|svg)$/.test(f));
|
||||
pruefe("keine fehlende Datei", verpasst.length === 0, verpasst.join(", ") || "keine");
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 280 KiB |
@@ -34,7 +34,7 @@
|
||||
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
|
||||
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
|
||||
Stand fest. */
|
||||
const CACHE_NAME = "dogfather-webdesign-v42";
|
||||
const CACHE_NAME = "dogfather-webdesign-v43";
|
||||
|
||||
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
|
||||
HTML-Datei in dieser Liste. */
|
||||
|
||||