Moderne iOS-Feature-Detection (ohne Schmerzen)
Moderne iOS-Feature-Erkennung ohne Aufwand: CSS.supports, Verhaltenstests und nur wenig UA-Sniffing für iOS 26 und alte iPads.
Feature-Detection ist der Standardweg: Prüfen Sie genau die Fähigkeit, die Sie tatsächlich brauchen – mit 'IntersectionObserver' in window oder CSS.supports('property', 'value') – und heben Sie sich User-Agent-Prüfungen für die wenigen iOS-Fälle auf, die sich wirklich nicht per Feature-Detection abbilden lassen.
Wer schon einmal eine Tabelle mit iOS-Versionsnummern in einer Codebasis gepflegt hat, kennt den Ablauf: Apple veröffentlicht ein Update, die Tabelle veraltet, und irgendetwas bricht in der Produktion, noch bevor jemand ein Ticket eröffnet. Capability-Prüfungen umgehen diesen Zyklus vollständig.
Feature-Detection löst die meisten Safari- und iOS-Probleme: Sie hören auf, brüchige Versionstabellen zu pflegen, und fragen stattdessen den Browser, was er kann. Der schwierige Teil ist der Rest: eine kleine Menge von iOS-Sonderfällen (alte iPads, die sich als Desktop-macOS ausgeben, das eingefrorene User-Agent-Feld ab iOS 26), für die es keine Capability-Prüfung gibt und bei denen Sie sorgfältig sniffen müssen. Dieser Artikel liefert Ihnen das moderne Vorgehen für beides.
Die wichtigsten Erkenntnisse
- Prüfen Sie Fähigkeiten direkt mit
'x' in window,CSS.supports()und Optional Chaining. MDN bezeichnet dies als „a much more reliable strategy” gegenüber dem Parsen des User-Agent-Strings. - Vorhandensein ist kein Beweis: Safari kann
@supportsfür ein Feature alstruemelden, das es tatsächlich gar nicht anwendet. Rendern Sie bei bekannten „Lügnern” das Element außerhalb des sichtbaren Bereichs und messen Sie es mitgetBoundingClientRect(). - Da jeder Browser unter iOS auf WebKit läuft und Safaris Version an die Major-Version des Betriebssystems gekoppelt ist, lässt sich mit einer
isMobileWebKit()-Prüfung plus einemCSS.supports()-Gate die iOS-Version ganz ohne UA-Parsing ableiten. - Ab iOS 26 friert Safari das OS-Token im UA auf einen Wert vor 26 ein, der sich seinerseits verändert hat (
18_6→18_6_2→18_7). Hardcoden Sie ihn niemals; parsen Sie stattdessen dasVersion/-Token. - Das Einfrieren ist ein Verhalten von Safari selbst, dokumentiert für iOS und iPadOS 26; Chrome und Firefox unter iOS melden weiterhin die tatsächliche iOS-Version.
Feature-Detection ist der Standard
Prüfen Sie die Fähigkeit, nicht den Browser. Eine Feature-Prüfung passt sich automatisch an, wenn Apple ein Update veröffentlicht, braucht keine Pflegetabelle und funktioniert engine-übergreifend identisch – genau deshalb empfehlen Apple und MDN sie gegenüber User-Agent-Sniffing. Dafür stehen Ihnen drei Werkzeuge zur Verfügung.
Für JavaScript-APIs prüfen Sie das globale Objekt oder nutzen Optional Chaining:
if ("IntersectionObserver" in window) {
// wire up lazy-loading
}
navigator.share?.({ title: "Modern feature detection" });
Für CSS verwenden Sie CSS.supports() in JS oder die At-Rule @supports in Ihrem Stylesheet:
@supports (text-wrap-style: stable) {
h1 { text-wrap-style: balance; }
}
Beachten Sie, dass navigator.userAgentData hier kein Fallback ist: Es ist Chromium-exklusiv und als experimentell gekennzeichnet, Safari und Firefox implementieren es also nicht. Als Ersatz für Feature-Detection taugt es nie, wenn Ihr Ziel iOS Safari ist.
Wenn Feature-Detection lügt: Vorhandensein ist kein Beweis
Discover how at OpenReplay.com.
Feature-Detection hat zwei Fehlermodi, die man benennen sollte, denn die Abhilfe unterscheidet sich. Der erste ist ein Anti-Pattern: ein unabhängiges Feature B zu prüfen, um auf Feature A zu schließen. Wie A Beautiful Site dokumentiert, bricht Ihre Prüfung in dem Moment stillschweigend, in dem der Browser das eine Feature vor dem anderen ausliefert. Koppeln Sie Prüfungen nicht an Stellvertreter.
Der zweite ist subtiler: Vorhandensein ist kein Beweis. Safari kann @supports für einen Wert als true melden, den es tatsächlich nicht anwendet – wie Evil Martians beim CSS-Keyword safe für Alignment festgestellt hat. Trauen Sie bei diesen bekannten „Lügnern” dem Support-Flag nicht. Rendern Sie das Element außerhalb des Sichtbereichs und messen Sie das tatsächliche Ergebnis mit getBoundingClientRect():
const supportsSafeAlign = () => {
const box = document.createElement("div");
const child = document.createElement("span");
child.textContent = "measure me";
Object.assign(box.style, {
display: "flex",
justifyContent: "safe center",
width: "5%",
position: "absolute",
top: "-9999px",
left: "-9999px",
});
box.appendChild(child);
document.body.appendChild(box);
const applied = child.getBoundingClientRect().left >= box.getBoundingClientRect().left;
document.body.removeChild(box);
return applied;
};
Ein Verhaltenstest braucht keine Versionsangabe, um korrekt zu sein: Er beobachtet, was tatsächlich gerendert wurde. Genau diese Fehlerklasse macht Session Replay gut sichtbar: ein Feature hinter einem Gate, das in Ihrer Testumgebung die Support-Prüfung besteht, sich auf dem realen iOS-Build eines Nutzers aber stillschweigend falsch verhält.
iOS-Versionen per Feature-Detection erkennen
Da jeder Browser unter iOS auf WebKit läuft und Safaris Version an die Major-Version des Betriebssystems gebunden ist, lässt sich mit einer isMobileWebKit()-Prüfung plus einem CSS.supports()-Gate auf eine Eigenschaft, die in einem bekannten Safari-Release eingeführt wurde, die iOS-Version ableiten – ganz ohne Parsen eines User-Agent-Strings. Die Mobile-WebKit-Heuristik stützt sich auf ein Gesture-Event, das WebKit bereitstellt:
const isMobileWebKit = () => "ongesturechange" in window;
Für das Versions-Gate schlagen Sie in den Safari-Release-Notes von Apple oder in MDNs Kompatibilitätsdaten nach, ab welchem Release eine Eigenschaft erstmals unterstützt wird, und testen sie dann. Die Longhand-Eigenschaft text-wrap-style kam in Safari 17.5, das seine Werte balance, stable und auto gemeinsam ausgeliefert hat – jeder dieser Werte eignet sich also sauber als Gate für iOS 17.5+:
const isAtLeastIOS175 = () =>
window.CSS?.supports("text-wrap-style", "stable") ?? false;
Verifizieren Sie die Zuordnung Eigenschaft ↔ Release selbst, bevor Sie ausliefern. Release-Notes lassen gelegentlich Änderungen aus, und ein „supported”-Flag kann lügen (siehe vorheriger Abschnitt). Behandeln Sie isMobileWebKit() als starke Heuristik, nicht als Spezifikationsgarantie: Apple erlaubt in der EU ab iOS 17.4 alternative Browser-Engines, „iOS bedeutet WebKit” trifft also überwiegend, aber nicht absolut zu.
Wann User-Agent-Sniffing die eng begrenzte letzte Möglichkeit ist
Greifen Sie nur dann zum User-Agent, wenn zwei Dinge gleichzeitig zutreffen: Für die Fähigkeit existiert tatsächlich kein Feature-Test, und eine falsche Annahme kostet Sie nicht mehr als einen kosmetischen Fehler. Zwei iOS-Fälle erfüllen diese Hürde.
Alte iPads. Feature-Detection kann ein iPad nicht von einem Mac unterscheiden, denn seit iPadOS 13 sendet ein iPad standardmäßig denselben User-Agent-String wie ein Mac. Kombinieren Sie stattdessen die Signale. Ein User-Agent, der wie Desktop-macOS-Safari aussieht, zusammen mit einer positiven Mobile-WebKit-Prüfung und einer Touch-Point-Anzahl größer null, bedeutet: ein iPad im Mac-Kostüm. Der Touch-Point-Test ist entscheidend: Macs melden navigator.maxTouchPoints als 0, das verhindert also einen Treffer bei einem echten Mac, selbst wenn das Gesture-Event-Signal auf Desktop-Safari vorhanden ist.
const looksLikeMacSafari = /Macintosh/.test(navigator.userAgent);
const isIPad = () =>
looksLikeMacSafari && isMobileWebKit() && navigator.maxTouchPoints > 0;
Das eingefrorene UA-Feld in iOS 26. Unter iOS 26 und iPadOS 26 hat Safari aufgehört, die laufende OS-Version in seinen User-Agent zu schreiben, und das Token stattdessen auf ein früheres Release fixiert. Dieser fixierte Wert hat sich über Point-Releases hinweg selbst verändert (18_6 zum Start, dann 18_6_2 in Safari 26.1, dann 18_7 ab iOS 26.2) – genau deshalb dürfen Sie ihn niemals hardcoden. Parsen Sie das Version/-Token, das nach wie vor die echte Safari-Major-Version (und damit die iOS-Major-Version) widerspiegelt:
const iosMajor = () => {
const m = navigator.userAgent.match(/Version\/(\d+)/);
return m ? Number(m[1]) : null; // 26 on iOS 26.x Safari
};
Zwei Punkte schärfen das Bild. Erstens betrifft das Einfrieren nur Safari: Unabhängige Server-Log-Analysen von AppleInsider und Lapcat Software bestätigen übereinstimmend, dass Chrome und Firefox unter iOS weiterhin die tatsächliche OS-Version melden. „Man kann iOS 26 nicht erkennen” ist also ein Safari-spezifisches Problem, kein iOS-weites. Zweitens: Wenn Sie das Parsing nicht selbst verantworten möchten – ua-parser-js steht in seinem aktuellen npm-Release bei 2.0.10, pinnen Sie also 2.0.10+. Das Paket hat zudem eine dokumentierte Historie von Supply-Chain-Vorfällen, prüfen Sie also genau, was Sie installieren.
Das Vorgehen im Überblick
Der komplette Ablauf ist kurz und läuft der Reihe nach:
- Zuerst Feature-Detection.
'x' in window,CSS.supports(),@supports, Optional Chaining. Das deckt die überwältigende Mehrheit aller Gating-Entscheidungen ab. - Die Lügner per Verhaltenstest prüfen. Wenn einem Support-Flag nicht zu trauen ist: außerhalb des Sichtbereichs rendern und mit
getBoundingClientRect()messen. - Die iOS-Version ohne UA ableiten.
isMobileWebKit()plus einCSS.supports()-Gate auf eine per Release-Notes bestätigte Eigenschaft. - Nur die nicht erkennbaren Randfälle sniffen. Die Kombinationsprüfung für alte iPads und das
Version/-Token für das Einfrieren in iOS 26. Setzen Sie sie nur dort ein, wo ein fehlgeschlagenes Sniffing keine Funktionalität kostet. - Auf echten Geräten und Simulatoren testen. Release-Notes und Support-Flags lassen beide Lücken, und nur echte Hardware schafft Klarheit.
Feature-Detection ist der Standard, weil sie Updates übersteht, die Sie nicht eingeplant haben; User-Agent-Sniffing ist die eng begrenzte Ausnahme für die zwei oder drei iOS-Fälle, die die Plattform tatsächlich unerkennbar macht. Verdrahten Sie die Prüfungen in dieser Reihenfolge, stützen Sie sich auf das Parsen des Version/-Tokens statt auf irgendeinen eingefrorenen OS-String, und bestätigen Sie das Ergebnis auf einem echten alten iPad, bevor Sie ausliefern.
FAQs
Lässt sich iOS 26 trotz des eingefrorenen User-Agents in JavaScript erkennen?
Ja. Ab iOS 26 friert Safari das OS-Token in seinem User-Agent auf einen Wert vor 26 ein, der sich über Point-Releases hinweg verändert hat (18_6, dann 18_6_2, dann 18_7 ab iOS 26.2), Hardcoden schlägt also fehl. Parsen Sie stattdessen das Version/-Token, das weiterhin die echte Safari-Major-Version und damit die iOS-Major-Version meldet. Unter iOS 26.x Safari liefert ein Match auf Version/(\\d+) den Wert 26.
Betrifft das eingefrorene User-Agent-Feld in iOS 26 auch Chrome und Firefox unter iOS?
Nein. Apple dokumentiert das eingefrorene OS-Token als Safari-eigenes Verhalten unter iOS und iPadOS 26. Unabhängige Server-Log-Analysen von AppleInsider und Lapcat Software bestätigen übereinstimmend, dass Chrome und Firefox unter iOS weiterhin die tatsächliche OS-Version in ihren User-Agent-Strings melden, obwohl jeder iOS-Browser auf WebKit läuft. 'Man kann iOS 26 nicht erkennen' ist also ein Safari-spezifisches Problem, kein iOS-weites.
Wie unterscheidet man im Browser ein iPad von einem Mac?
Kombinieren Sie zwei Signale, denn Feature-Detection allein kann sie nicht trennen: Seit iPadOS 13 sendet ein iPad standardmäßig denselben User-Agent-String wie ein Mac. Wenn der User-Agent wie Desktop-macOS-Safari aussieht, eine Mobile-WebKit-Prüfung aber auf ein Touch-Gerät hindeutet, haben Sie ein iPad, das sich als Mac ausgibt. Da Gesture-Events auch auf Desktop-Safari vorkommen, prüfen Sie zusätzlich, ob navigator.maxTouchPoints größer als 0 ist, denn Macs melden 0.
Funktioniert navigator.userAgentData in Safari?
Nein. navigator.userAgentData ist Chromium-exklusiv und auf MDN als experimentell gekennzeichnet, Safari und Firefox implementieren es also nicht. Als Ersatz für Feature-Detection ist es nie tragfähig, wenn Ihr Ziel iOS Safari ist. Prüfen Sie Fähigkeiten stattdessen direkt mit 'x' in window oder CSS.supports() – laut MDN ein weitaus verlässlicherer Ansatz als das Auslesen des User-Agents.