Kundenansicht neu: Leiste im Chat am iPhone wieder ganz unten
Die Messung am iPhone (iOS 26.2, App vom Home-Bildschirm) zeigt die
Ursache: Auf der Startseite ist das Fenster 1002 px hoch. Im Chat
schrumpft die Spalte auf den Kopf (alles andere steht fest), die Seite
wird kürzer als der Bildschirm, und iOS hält unten rund 60 pt für eine
Safari-Leiste frei, die es in der App nicht gibt: 932 px, Leiste und
Eingabe stehen zu hoch, darunter ein weißer Streifen. Mit der Tastatur
hat das nichts zu tun.
Am Handy hat die Spalte im Chat jetzt wie in den anderen Bereichen
mindestens volle Bildschirmhöhe (100vh, dann 100lvh). Ohne
Browserleisten scrollt die Seite dadurch nicht; in Chromium sind alle
Chat-Bilder am Handy und Desktop pixelgleich zu main.
Der Höhen-Wächter aus 0fa8fdb (Hülle aus- und einblenden) hat nicht
geholfen und ist wieder raus. Die Messung bleibt für die Kontrolle am
iPhone und wird danach entfernt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cca29a4aef
commit
17bce304eb
6 files changed
+28
-224
No files matched your search
@@ -1,12 +1,12 @@
|
||||
/* VORÜBERGEHENDE MESSUNG (2026-10-29), wird nach der Klärung wieder entfernt: Am iPhone (App vom Home-Bildschirm) steht die Leiste zu hoch.
|
||||
Schickt Bildschirmwerte (Fenster, visualViewport, Bildschirm, Safe-Area, vh-Einheiten, Lage der Leiste, Zustand von hoehe.js) an
|
||||
Schickt Bildschirmwerte (Fenster, visualViewport, Bildschirm, Safe-Area, vh-Einheiten, Lage der Leiste) an
|
||||
/api/diag/hoehe; der Server schreibt sie ins Log (server/routes/diagHoehe.js). Keine Inhalte, keine Eingaben. Höchstens MAX Meldungen je Seite. */
|
||||
|
||||
const MAX = 80
|
||||
const ZIEL = '/api/diag/hoehe'
|
||||
const rund = (x) => (Number.isFinite(x) ? Math.round(x * 10) / 10 : null)
|
||||
|
||||
export function starteDiagHoehe({ win = window, doc = document, hoehe = null }) {
|
||||
export function starteDiagHoehe({ win = window, doc = document }) {
|
||||
const t0 = Date.now()
|
||||
let n = 0
|
||||
const probe = (stil) => {
|
||||
@@ -38,7 +38,6 @@ export function starteDiagHoehe({ win = window, doc = document, hoehe = null })
|
||||
unten: rund(unten.getBoundingClientRect().bottom),
|
||||
leiste: leiste ? [rund(leiste.top), rund(leiste.bottom), rund(leiste.height)] : null,
|
||||
huelle: huelle ? [rund(huelle.top), rund(huelle.height)] : null,
|
||||
hoehe: hoehe?.stand?.() ?? null,
|
||||
app: Boolean(win.matchMedia?.('(display-mode: standalone)').matches), ns: win.navigator?.standalone ?? null,
|
||||
ori: win.screen?.orientation?.type ?? null,
|
||||
fokus: aktiv && aktiv !== doc.body ? aktiv.tagName : null,
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/* Kundenansicht neu: iOS-Fehler der App vom Home-Bildschirm. Nach dem Schließen der Tastatur bleibt die Fensterhöhe oft kleiner
|
||||
(WebKit, bekannt seit iOS 17, in iOS 26 wieder da): alles, was unten fest steht (Leiste, Chat-Eingabe), sitzt dann rund 60 px zu hoch,
|
||||
darunter bleibt ein leerer weißer Streifen, bis die App neu startet. Abhilfe: die Hülle (mindestens volle Fensterhöhe) kurz aus- und
|
||||
wieder einblenden, das zwingt WebKit, die Höhe neu zu messen. Scrollstände und Fokus bleiben. Nur in der App vom Home-Bildschirm auf
|
||||
iOS (das entscheidet `aktiv`) und nur, wenn die Höhe wirklich hängt. */
|
||||
|
||||
export const LUFT = 4 // so viel darf die Höhe schwanken, ohne dass sie als hängend gilt
|
||||
export const PRUEF_MS = [150, 600] // nach dem Schließen der Tastatur: früh und noch einmal, wenn die Animation sicher durch ist
|
||||
export const MAX_VERSUCHE = 2 // je Anlass (Tastatur zu, App wieder vorne); hilft es nicht, bleibt es bis zum nächsten Anlass dabei
|
||||
|
||||
/** Reiner Zustand { voll, breite }: größte innere Höhe bei dieser Breite. Ein Breitenwechsel (Drehen, iPad-Fenster) fängt neu an. */
|
||||
export function hoeheZustand(alt, { hoehe, breite }) {
|
||||
if (!alt || Math.abs(breite - alt.breite) > 1) return { voll: hoehe, breite }
|
||||
return { voll: Math.max(alt.voll, hoehe), breite: alt.breite }
|
||||
}
|
||||
|
||||
/** Hängt die Höhe? Kleiner als die größte bei gleicher Breite und kein Textfeld im Fokus (eine offene Tastatur ist kein Hängen). */
|
||||
export function haengt(zustand, { hoehe, breite, textfeldAktiv }) {
|
||||
if (!zustand || textfeldAktiv) return false
|
||||
if (Math.abs(breite - zustand.breite) > 1) return false
|
||||
return zustand.voll - hoehe > LUFT
|
||||
}
|
||||
|
||||
/**
|
||||
* Verdrahtung: merkt sich die volle Höhe (resize), prüft nach dem Verlassen eines Feldes (focusout), nach einem resize ohne Textfeld
|
||||
* und wenn die App wieder nach vorne kommt (visibilitychange), jeweils zu den Zeiten PRUEF_MS. onHeile({ vorher, nachher, voll, versuche }) nach
|
||||
* jedem Eingriff. Rückgabe { pruefe, stand, beende }.
|
||||
*/
|
||||
export function beobachteHoehe({ win = window, doc = document, element, aktiv = () => true, istTextfeld = () => false, onHeile = () => {} }) {
|
||||
const messe = () => ({ hoehe: win.innerHeight, breite: win.innerWidth })
|
||||
let z = hoeheZustand(null, messe())
|
||||
let versuche = 0
|
||||
let timer = []
|
||||
const merke = () => { z = hoeheZustand(z, messe()) }
|
||||
|
||||
/** Hülle aus- und einblenden, mit synchronem Umbruch dazwischen; vorher gescrollte Kästen, Fensterscroll und Fokus zurücksetzen. */
|
||||
function heile() {
|
||||
const fensterX = win.scrollX ?? 0
|
||||
const fensterY = win.scrollY ?? 0
|
||||
const staende = []
|
||||
for (const el of element.querySelectorAll('*')) {
|
||||
if (el.scrollTop || el.scrollLeft) staende.push([el, el.scrollTop, el.scrollLeft])
|
||||
}
|
||||
const fokus = doc.activeElement && doc.activeElement !== doc.body && element.contains(doc.activeElement) ? doc.activeElement : null
|
||||
const vorher = element.style.display
|
||||
element.style.display = 'none'
|
||||
void element.offsetHeight
|
||||
element.style.display = vorher
|
||||
void element.offsetHeight
|
||||
for (const [el, top, left] of staende) { el.scrollTop = top; el.scrollLeft = left }
|
||||
if (fensterX || fensterY) win.scrollTo(fensterX, fensterY)
|
||||
if (fokus && doc.activeElement !== fokus) fokus.focus?.({ preventScroll: true })
|
||||
}
|
||||
|
||||
function pruefe() {
|
||||
if (!aktiv() || !element) return false
|
||||
const m = messe()
|
||||
if (!haengt(z, { ...m, textfeldAktiv: istTextfeld(doc.activeElement) })) { merke(); return false }
|
||||
if (versuche >= MAX_VERSUCHE) return false
|
||||
versuche += 1
|
||||
const vorher = win.innerHeight
|
||||
heile()
|
||||
merke()
|
||||
try { onHeile({ vorher, nachher: win.innerHeight, voll: z.voll, versuche }) } catch { /* nur Beobachter */ }
|
||||
return true
|
||||
}
|
||||
|
||||
const warte = (fn, ms) => (win.setTimeout ? win.setTimeout(fn, ms) : setTimeout(fn, ms))
|
||||
const stoppe = (id) => (win.clearTimeout ? win.clearTimeout(id) : clearTimeout(id))
|
||||
const plane = () => {
|
||||
for (const id of timer) stoppe(id)
|
||||
timer = PRUEF_MS.map((ms) => warte(pruefe, ms))
|
||||
}
|
||||
const neuerAnlass = () => { versuche = 0; plane() }
|
||||
|
||||
const beiResize = () => { merke(); if (!istTextfeld(doc.activeElement)) plane() }
|
||||
const beiUnfokus = () => neuerAnlass()
|
||||
const beiSicht = () => { if (doc.visibilityState === 'visible') neuerAnlass() }
|
||||
win.addEventListener('resize', beiResize)
|
||||
doc.addEventListener('focusout', beiUnfokus)
|
||||
doc.addEventListener('visibilitychange', beiSicht)
|
||||
return {
|
||||
pruefe,
|
||||
stand: () => ({ ...z, versuche }),
|
||||
beende() {
|
||||
for (const id of timer) stoppe(id)
|
||||
win.removeEventListener('resize', beiResize)
|
||||
doc.removeEventListener('focusout', beiUnfokus)
|
||||
doc.removeEventListener('visibilitychange', beiSicht)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,10 @@
|
||||
#chat-body { display: flex; flex: 1 1 auto; flex-direction: column; min-height: 0; }
|
||||
|
||||
@media (max-width: 880px), (pointer: coarse) and (max-height: 500px) {
|
||||
.kn-spalte:has(#bereich-chat:not([hidden])) { display: block; height: auto; }
|
||||
/* Mindestens volle Bildschirmhöhe wie die anderen Bereiche (.kn-spalte, 100vh): ist die Seite kürzer, hält iOS 26 in der App vom Home-Bildschirm
|
||||
unten rund 60 pt für eine Safari-Leiste frei, die es dort nicht gibt. Das Fenster wird kleiner (gemessen am iPhone 2026-10-29: 1002 → 932 px),
|
||||
Leiste und Eingabe stehen zu hoch, darunter ein weißer Streifen. lvh = Höhe ohne Browserleisten; die Seite scrollt dadurch nicht. */
|
||||
.kn-spalte:has(#bereich-chat:not([hidden])) { display: block; height: auto; min-height: 100vh; min-height: 100lvh; }
|
||||
.kn-haupt:has(#bereich-chat:not([hidden])) { position: fixed; inset: calc(var(--kn-banner-h) + var(--kn-kopf-h) + env(safe-area-inset-top)) 0 var(--kn-unten, var(--kn-zeile-h)); padding: 0 max(16px, env(safe-area-inset-right)) 6px max(16px, env(safe-area-inset-left)); background: var(--kn-bg); }
|
||||
#bereich-chat > .kn-titel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } /* bleibt für Screenreader die Überschrift der Seite */
|
||||
.kn:has(#bereich-chat:not([hidden])) .kn-kopf { height: calc(var(--kn-kopf-h) + env(safe-area-inset-top)); border-bottom-color: var(--kn-line); } /* feste Höhe wie der Rand oben des Inhalts: im Querformat wäre der Kopf mit Haarlinie 1 px höher */
|
||||
|
||||
@@ -7,8 +7,7 @@ import { h, leeren, text } from './dom.js'
|
||||
import { api as apiStandard } from './api.js'
|
||||
import * as routerStandard from './router.js'
|
||||
import { BEREICHE, FUNKTIONS_ETAGEN, hashFuerAnsicht, hashFuerRoute, elternVon, titelFuer, zeigtWiderrufsstreifen } from './routen.js'
|
||||
import { HANDY_ABFRAGE, baueNavigation, beobachteTastatur, istTextfeld, leisteSichtbar } from './leiste.js'
|
||||
import { beobachteHoehe } from './hoehe.js'
|
||||
import { HANDY_ABFRAGE, baueNavigation, beobachteTastatur, leisteSichtbar } from './leiste.js'
|
||||
import { starteDiagHoehe } from './diag-hoehe.js'
|
||||
import { baueEtage } from './etage.js'
|
||||
import { erzeugeZaehler } from './zaehler.js'
|
||||
@@ -279,11 +278,8 @@ export async function starteHuelle({ win = window, doc = document, api = apiStan
|
||||
if (handyMq.addEventListener) handyMq.addEventListener('change', beiMedien)
|
||||
else handyMq.addListener?.(beiMedien)
|
||||
beobachteTastatur({ win, doc, onChange: (z) => doc.body.classList.toggle('kn-tastatur', !leisteSichtbar(z)) })
|
||||
// iOS-App vom Home-Bildschirm: hängt nach der Tastatur die Fensterhöhe, steht die Leiste zu hoch (hoehe.js); dann einmal neu vermessen
|
||||
let diag = null
|
||||
const hoehe = beobachteHoehe({ win, doc, element: doc.querySelector('.kn-huelle'), aktiv: () => istIos() && standalone() && istHandy(), istTextfeld, onHeile: (werte) => diag?.sende('heile', werte) })
|
||||
// VORÜBERGEHENDE MESSUNG (2026-10-29, diag-hoehe.js): Bildschirmwerte am iPhone ins Server-Log, wird nach der Klärung entfernt
|
||||
if (istIos() && istHandy() && !viewAs) diag = starteDiagHoehe({ win, doc, hoehe })
|
||||
if (istIos() && istHandy() && !viewAs) starteDiagHoehe({ win, doc })
|
||||
|
||||
// 7. Fristen und Zähler laden parallel und brechen den Start nie ab
|
||||
ladeFristen(api).then((daten) => { fristen = daten; streifen.setze(daten); aktualisiereKonto() })
|
||||
|
||||
Reference in new issue
Block a user