Kundenansicht neu: kein Hineinzoomen beim Tippen auf dem iPhone
Messung am iPhone: die Seite ist verkleinert gezeichnet (462 px Breite auf 393 pt, visualViewport.scale 0,85, wohl Seitenzoom 85 %). Unsere 16-px-Felder haben auf dem Bildschirm dann nur 13,6 pt, iOS zoomt beim Antippen auf 100 % hinein und danach nicht zurück: Ausschnitt, rechts abgeschnitten, die Eingabe liegt hinter der Tastatur. zoom-ausgleich.js setzt auf iOS bei einem Maßstab unter 0,99 die Klasse kn-zoom-ausgleich und den Faktor 1,01/scale an <html>; huelle.css gibt dann allen Textfeldern der Kundenansicht 16 px mal Faktor, also auf dem Bildschirm wieder 16 pt. Nie umgestellt, während ein Feld den Fokus hat. Bei 100 % ändert sich nichts (Chromium: pixelgleich zu main). Die Messung meldet zusätzlich Maßstab (3 Stellen), Faktor, Seitenbreite und Schriftgröße des Feldes im Fokus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
17bce304eb
commit
1a09bebe41
6 files changed
+99
-5
No files matched your search
@@ -6,7 +6,7 @@ const MAX = 80
|
|||||||
const ZIEL = '/api/diag/hoehe'
|
const ZIEL = '/api/diag/hoehe'
|
||||||
const rund = (x) => (Number.isFinite(x) ? Math.round(x * 10) / 10 : null)
|
const rund = (x) => (Number.isFinite(x) ? Math.round(x * 10) / 10 : null)
|
||||||
|
|
||||||
export function starteDiagHoehe({ win = window, doc = document }) {
|
export function starteDiagHoehe({ win = window, doc = document, zoom = null }) {
|
||||||
const t0 = Date.now()
|
const t0 = Date.now()
|
||||||
let n = 0
|
let n = 0
|
||||||
const probe = (stil) => {
|
const probe = (stil) => {
|
||||||
@@ -31,7 +31,9 @@ export function starteDiagHoehe({ win = window, doc = document }) {
|
|||||||
iw: win.innerWidth, ih: win.innerHeight, ow: win.outerWidth, oh: win.outerHeight,
|
iw: win.innerWidth, ih: win.innerHeight, ow: win.outerWidth, oh: win.outerHeight,
|
||||||
cw: doc.documentElement.clientWidth, ch: doc.documentElement.clientHeight, dh: doc.documentElement.scrollHeight,
|
cw: doc.documentElement.clientWidth, ch: doc.documentElement.clientHeight, dh: doc.documentElement.scrollHeight,
|
||||||
sw: win.screen?.width, sh: win.screen?.height, sah: win.screen?.availHeight, dpr: win.devicePixelRatio,
|
sw: win.screen?.width, sh: win.screen?.height, sah: win.screen?.availHeight, dpr: win.devicePixelRatio,
|
||||||
vv: vv ? [rund(vv.width), rund(vv.height), rund(vv.offsetTop), rund(vv.pageTop), rund(vv.scale)] : null,
|
vv: vv ? [rund(vv.width), rund(vv.height), rund(vv.offsetTop), rund(vv.pageTop), Math.round(vv.scale * 1000) / 1000] : null,
|
||||||
|
zf: zoom?.faktor?.() ?? null, swd: doc.documentElement.scrollWidth,
|
||||||
|
fs: aktiv && aktiv !== doc.body ? win.getComputedStyle(aktiv).fontSize : null,
|
||||||
sy: rund(win.scrollY),
|
sy: rund(win.scrollY),
|
||||||
sa: [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map((p) => rund(parseFloat(p))),
|
sa: [cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft].map((p) => rund(parseFloat(p))),
|
||||||
vh: [vh.offsetHeight, dvh.offsetHeight, svh.offsetHeight, lvh.offsetHeight],
|
vh: [vh.offsetHeight, dvh.offsetHeight, svh.offsetHeight, lvh.offsetHeight],
|
||||||
|
|||||||
@@ -195,6 +195,11 @@
|
|||||||
.kn:has(#bereich-chat:not([hidden])) .kn-kopf-titel { opacity: 1; transform: none; }
|
.kn:has(#bereich-chat:not([hidden])) .kn-kopf-titel { opacity: 1; transform: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- iOS bei verkleinerter Seite (zoom-ausgleich.js) ----------
|
||||||
|
Ist die Seite kleiner gezeichnet (Seitenzoom unter 100 %), hätten 16-px-Felder auf dem Bildschirm weniger als 16 pt, und iOS zoomt beim Tippen
|
||||||
|
hinein und nicht wieder heraus. Die Klasse an <html> setzt nur zoom-ausgleich.js; der Faktor gleicht den Maßstab aus. */
|
||||||
|
.kn-zoom-ausgleich .kn :is(input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]), textarea, select) { font-size: calc(16px * var(--kn-zoom-ausgleich, 1)) !important; }
|
||||||
|
|
||||||
/* ---------- Querformat (Handy gedreht): niedrigere Zeile, keine große Überschrift ---------- */
|
/* ---------- Querformat (Handy gedreht): niedrigere Zeile, keine große Überschrift ---------- */
|
||||||
|
|
||||||
@media (max-height: 500px) and (max-width: 880px), (max-height: 500px) and (pointer: coarse) {
|
@media (max-height: 500px) and (max-width: 880px), (max-height: 500px) and (pointer: coarse) {
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { h, leeren, text } from './dom.js'
|
|||||||
import { api as apiStandard } from './api.js'
|
import { api as apiStandard } from './api.js'
|
||||||
import * as routerStandard from './router.js'
|
import * as routerStandard from './router.js'
|
||||||
import { BEREICHE, FUNKTIONS_ETAGEN, hashFuerAnsicht, hashFuerRoute, elternVon, titelFuer, zeigtWiderrufsstreifen } from './routen.js'
|
import { BEREICHE, FUNKTIONS_ETAGEN, hashFuerAnsicht, hashFuerRoute, elternVon, titelFuer, zeigtWiderrufsstreifen } from './routen.js'
|
||||||
import { HANDY_ABFRAGE, baueNavigation, beobachteTastatur, leisteSichtbar } from './leiste.js'
|
import { HANDY_ABFRAGE, baueNavigation, beobachteTastatur, istTextfeld, leisteSichtbar } from './leiste.js'
|
||||||
|
import { beobachteZoom } from './zoom-ausgleich.js'
|
||||||
import { starteDiagHoehe } from './diag-hoehe.js'
|
import { starteDiagHoehe } from './diag-hoehe.js'
|
||||||
import { baueEtage } from './etage.js'
|
import { baueEtage } from './etage.js'
|
||||||
import { erzeugeZaehler } from './zaehler.js'
|
import { erzeugeZaehler } from './zaehler.js'
|
||||||
@@ -278,8 +279,10 @@ export async function starteHuelle({ win = window, doc = document, api = apiStan
|
|||||||
if (handyMq.addEventListener) handyMq.addEventListener('change', beiMedien)
|
if (handyMq.addEventListener) handyMq.addEventListener('change', beiMedien)
|
||||||
else handyMq.addListener?.(beiMedien)
|
else handyMq.addListener?.(beiMedien)
|
||||||
beobachteTastatur({ win, doc, onChange: (z) => doc.body.classList.toggle('kn-tastatur', !leisteSichtbar(z)) })
|
beobachteTastatur({ win, doc, onChange: (z) => doc.body.classList.toggle('kn-tastatur', !leisteSichtbar(z)) })
|
||||||
|
// iOS: bei verkleinerter Seite die Feldschrift so vergrößern, dass iOS beim Tippen nicht hineinzoomt (zoom-ausgleich.js)
|
||||||
|
const zoom = istIos() ? beobachteZoom({ win, doc, istTextfeld }) : null
|
||||||
// VORÜBERGEHENDE MESSUNG (2026-10-29, diag-hoehe.js): Bildschirmwerte am iPhone ins Server-Log, wird nach der Klärung entfernt
|
// 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) starteDiagHoehe({ win, doc })
|
if (istIos() && istHandy() && !viewAs) starteDiagHoehe({ win, doc, zoom })
|
||||||
|
|
||||||
// 7. Fristen und Zähler laden parallel und brechen den Start nie ab
|
// 7. Fristen und Zähler laden parallel und brechen den Start nie ab
|
||||||
ladeFristen(api).then((daten) => { fristen = daten; streifen.setze(daten); aktualisiereKonto() })
|
ladeFristen(api).then((daten) => { fristen = daten; streifen.setze(daten); aktualisiereKonto() })
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/* Kundenansicht neu: iOS zoomt beim Antippen eines Feldes hinein, wenn dessen Schrift auf dem Bildschirm kleiner als 16 pt ist, und zoomt danach
|
||||||
|
nicht zurück: man sieht nur noch einen Ausschnitt, rechts ist alles abgeschnitten, die Eingabe liegt hinter der Tastatur. Unsere Felder haben am
|
||||||
|
Handy 16 px. Ist die Seite aber verkleinert gezeichnet (Seitenzoom in Safari unter 100 %; gemessen am iPhone 2026-10-29: 85 %,
|
||||||
|
visualViewport.scale 0,85), sind das auf dem Bildschirm nur 13,6 pt. Dann bekommen alle Textfelder so viel größere Schrift, dass sie auf dem
|
||||||
|
Bildschirm wieder 16 pt hat: Klasse kn-zoom-ausgleich und Faktor --kn-zoom-ausgleich an <html>, die Regel steht in huelle.css. Bei 100 % passiert nichts. */
|
||||||
|
|
||||||
|
/** Faktor für die Feldschrift beim Maßstab `scale`; 1 = kein Ausgleich (100 % oder hineingezoomt). 1 % Luft gegen Rundung, auf drei Stellen. */
|
||||||
|
export function ausgleichFaktor(scale) {
|
||||||
|
if (!Number.isFinite(scale) || scale <= 0 || scale >= 0.99) return 1
|
||||||
|
return Math.ceil((1.01 / scale) * 1000) / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stellt den Faktor beim Start und bei jeder Maßstabsänderung (visualViewport resize) ein, nie während ein Textfeld den Fokus hat
|
||||||
|
* (iOS hat dann schon entschieden). Rückgabe { faktor, beende }.
|
||||||
|
*/
|
||||||
|
export function beobachteZoom({ win = window, doc = document, istTextfeld = () => false }) {
|
||||||
|
const vv = win.visualViewport
|
||||||
|
if (!vv) return { faktor: () => 1, beende() {} }
|
||||||
|
const wurzel = doc.documentElement
|
||||||
|
let faktor = 1
|
||||||
|
const setze = () => {
|
||||||
|
if (istTextfeld(doc.activeElement)) return
|
||||||
|
const neu = ausgleichFaktor(vv.scale)
|
||||||
|
if (neu === faktor) return
|
||||||
|
faktor = neu
|
||||||
|
if (faktor === 1) {
|
||||||
|
wurzel.classList.remove('kn-zoom-ausgleich')
|
||||||
|
wurzel.style.removeProperty('--kn-zoom-ausgleich')
|
||||||
|
} else {
|
||||||
|
wurzel.style.setProperty('--kn-zoom-ausgleich', String(faktor))
|
||||||
|
wurzel.classList.add('kn-zoom-ausgleich')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setze()
|
||||||
|
vv.addEventListener('resize', setze)
|
||||||
|
return { faktor: () => faktor, beende() { vv.removeEventListener('resize', setze) } }
|
||||||
|
}
|
||||||
@@ -16,5 +16,5 @@ test('huelle.css: am Handy hat die Spalte im Chat mindestens volle Höhe (100vh,
|
|||||||
test('huelle.js: der alte Höhen-Wächter (Hülle aus- und einblenden) ist weg, die Messung läuft nur am iPhone und nicht in der Mitarbeiter-Ansicht', () => {
|
test('huelle.js: der alte Höhen-Wächter (Hülle aus- und einblenden) ist weg, die Messung läuft nur am iPhone und nicht in der Mitarbeiter-Ansicht', () => {
|
||||||
const js = lies('huelle.js')
|
const js = lies('huelle.js')
|
||||||
assert.doesNotMatch(js, /beobachteHoehe|'\.\/hoehe\.js'/)
|
assert.doesNotMatch(js, /beobachteHoehe|'\.\/hoehe\.js'/)
|
||||||
assert.match(js, /if \(istIos\(\) && istHandy\(\) && !viewAs\) starteDiagHoehe\(\{ win, doc \}\)/)
|
assert.match(js, /if \(istIos\(\) && istHandy\(\) && !viewAs\) starteDiagHoehe\(\{ win, doc, zoom \}\)/)
|
||||||
})
|
})
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { ausgleichFaktor, beobachteZoom } from '../public/neu/zoom-ausgleich.js'
|
||||||
|
import { lies } from './support/knHuelle.js'
|
||||||
|
|
||||||
|
test('ausgleichFaktor: 85 % (iPhone 2026-10-29) ergibt 16 px × 1,188 ≈ 16,2 pt auf dem Bildschirm; 100 %, hineingezoomt und Unsinn ergeben 1', () => {
|
||||||
|
const f = ausgleichFaktor(0.8506)
|
||||||
|
assert.equal(f, 1.188)
|
||||||
|
assert.ok(16 * f * 0.8506 >= 16)
|
||||||
|
assert.equal(ausgleichFaktor(0.5), 2.02)
|
||||||
|
for (const s of [1, 0.99, 1.5, 0, -1, NaN, undefined]) assert.equal(ausgleichFaktor(s), 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
function aufbau(scale) {
|
||||||
|
const hoerer = []
|
||||||
|
const vv = { scale, addEventListener: (t, fn) => hoerer.push(fn), removeEventListener: (t, fn) => hoerer.splice(hoerer.indexOf(fn), 1), feuere: () => hoerer.forEach((fn) => fn()) }
|
||||||
|
const klassen = new Set(); const stil = {}
|
||||||
|
const doc = {
|
||||||
|
activeElement: null,
|
||||||
|
documentElement: {
|
||||||
|
classList: { add: (k) => klassen.add(k), remove: (k) => klassen.delete(k) },
|
||||||
|
style: { setProperty: (k, v) => { stil[k] = v }, removeProperty: (k) => { delete stil[k] } },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return { vv, doc, klassen, stil, hoerer, win: { visualViewport: vv } }
|
||||||
|
}
|
||||||
|
|
||||||
|
test('beobachteZoom: setzt Klasse und Faktor bei 85 %, nimmt sie bei 100 % weg, ändert nichts während ein Textfeld den Fokus hat', () => {
|
||||||
|
const t = aufbau(0.8506)
|
||||||
|
const feld = { feld: true }
|
||||||
|
const z = beobachteZoom({ win: t.win, doc: t.doc, istTextfeld: (el) => el?.feld === true })
|
||||||
|
assert.deepEqual([[...t.klassen], t.stil, z.faktor()], [['kn-zoom-ausgleich'], { '--kn-zoom-ausgleich': '1.188' }, 1.188])
|
||||||
|
t.doc.activeElement = feld; t.vv.scale = 1; t.vv.feuere() // iOS zoomt beim Tippen: nichts umstellen
|
||||||
|
assert.equal(z.faktor(), 1.188)
|
||||||
|
t.doc.activeElement = null; t.vv.feuere()
|
||||||
|
assert.deepEqual([[...t.klassen], t.stil, z.faktor()], [[], {}, 1])
|
||||||
|
z.beende(); assert.equal(t.hoerer.length, 0)
|
||||||
|
const ohne = beobachteZoom({ win: {}, doc: t.doc }); assert.equal(ohne.faktor(), 1); ohne.beende()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('huelle.css und huelle.js: Ausgleich nur über die Klasse an <html>, für alle Textfelder der Kundenansicht, nur auf iOS verdrahtet', () => {
|
||||||
|
const css = lies('huelle.css')
|
||||||
|
assert.match(css, /\.kn-zoom-ausgleich \.kn :is\(input:not\(\[type="checkbox"\]\)[^{]*, textarea, select\) \{ font-size: calc\(16px \* var\(--kn-zoom-ausgleich, 1\)\) !important; \}/)
|
||||||
|
const js = lies('huelle.js')
|
||||||
|
assert.match(js, /const zoom = istIos\(\) \? beobachteZoom\(\{ win, doc, istTextfeld \}\) : null/)
|
||||||
|
})
|
||||||
Reference in new issue
Block a user