Diese Seite wurde maschinell übersetzt und kann Fehler enthalten. Das englische Original lesen

Elemente

Sicherer Bereich

Der Safe Area hält Inhalte frei von der Notch bzw. der Dynamic Island, der Statusleiste, dem Home-Indikator, einem Navigations-Header und der Mac-Catalyst-Titelleiste. Jede View kann sich per CSS-env() um diesen Bereich einrücken: env(safe-area-inset-top), env(safe-area-inset-right), env(safe-area-inset-bottom) und env(safe-area-inset-left), in ihrem Padding oder Margin. Das sind die Insets der Root-View, in der der Knoten gerendert wird: innerhalb einer NavPage die Insets unterhalb des Headers der Seite, innerhalb eines Dialog die Insets des Dialogs und andernfalls der Safe Area der Surface. In einem Browser ist die Seite die Root, und die Variablen sind die des Browsers selbst.

el-safe-area.tsx
el-safe-area.tsxLive
import { mount, signal, Text, View } from "nsh";
import { safeArea, safeAreaInsets } from "nsh";
import { button, buttonText, desktop, tint, ui } from "./palette";

type Edge = "top" | "right" | "bottom" | "left";

const EDGES: Edge[] = ["top", "right", "bottom", "left"];

// `calc(env(safe-area-inset-<edge>) + 16px)` on the edges chosen, 16 px on the others.
const side = (edges: Edge[], e: Edge) => (edges.includes(e) ? (`calc(env(safe-area-inset-${e}) + 16px)` as const) : 16);
const padding = (edges: Edge[]) => ({
  paddingTop: side(edges, "top"),
  paddingRight: side(edges, "right"),
  paddingBottom: side(edges, "bottom"),
  paddingLeft: side(edges, "left"),
});

const format = (i: { top: number; right: number; bottom: number; left: number }) =>
  `top ${Math.round(i.top)} · right ${Math.round(i.right)} · bottom ${Math.round(i.bottom)} · left ${Math.round(i.left)}`;

// Called in a component: the insets where it renders (the root here, so the
// surface's safe area; inside a page or a dialog, theirs).
function Insets() {
  const insets = safeAreaInsets();
  return <Text style={{ color: ui().text, fontVariant: ["tabular-nums"] }}>{format(insets())}</Text>;
}

function App() {
  const edges = signal<Edge[]>([...EDGES]);
  const toggle = (edge: Edge) => edges.update((list) => (list.includes(edge) ? list.filter((e) => e !== edge) : EDGES.filter((e) => e === edge || list.includes(e))));
  const label = () => ({ fontSize: 12, color: ui().muted });
  return (
    // The tinted band around the card is the view's padding: the insets of
    // the edges chosen, plus 16 pt.
    <View style={{ flex: 1, backgroundColor: tint(ui().accent, 0.16), ...padding(edges()) }}>
      <View
        style={{
          flex: 1,
          width: "100%",
          maxWidth: 560,
          alignSelf: "center",
          backgroundColor: ui().page,
          borderRadius: 16,
          borderWidth: 1,
          borderStyle: "dashed",
          borderColor: ui().faint,
          padding: 20,
          gap: 14,
        }}
      >
        <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>safe area insets</Text>
        <Text style={{ color: ui().muted, lineHeight: 20 }}>
          {desktop
            ? "A desktop window or a browser page has no system UI over it: the insets are 0, so the view pads only by its 16 pt (the tinted band)."
            : "On a phone the insets are the status bar or notch on top and the home indicator below; env(safe-area-inset-*) adds them to its 16 pt. This browser frame has none, so they are 0 here."}
        </Text>

        <Text style={label()}>edges ({desktop ? "click" : "tap"} to toggle)</Text>
        <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
          <Each of={EDGES}>
            {(edge) => {
              const on = () => edges().includes(edge());
              return (
                <View
                  role="button"
                  onPress={() => toggle(edge())}
                  {...button("secondary", { on })}
                >
                  <Text style={buttonText("secondary", { on })}>{edge()}</Text>
                </View>
              );
            }}
          </Each>
        </View>

        <View style={{ ...ui().inset, borderRadius: 12, padding: 14, gap: 6 }}>
          <Text style={label()}>edges</Text>
          <Text style={{ color: ui().text }}>{edges().length ? `[${edges().map((e) => `"${e}"`).join(", ")}]` : "[] (no inset padded)"}</Text>
          <Text style={label()}>safeArea() · the surface's insets</Text>
          <Text style={{ color: ui().text, fontVariant: ["tabular-nums"] }}>{format(safeArea())}</Text>
          <Text style={label()}>safeAreaInsets() · where it renders</Text>
          <Insets />
        </View>
      </View>
    </View>
  );
}

mount(App);
use nsh_core::prelude::*;

fn app() -> impl IntoElement {
    view()
        .flex_1()
        .bg(rgb(0xfff8c5))
        .p_safe_area()
        .child(text("Clear of the notch").text_2xl().font_weight(FontWeight::BOLD))
}

Die Hintergrundfarbe füllt den gesamten Bereich aus, einschließlich des Bereichs hinter der Statusleiste, und der Inhalt beginnt darunter. Die Werte folgen den Insets: Bei Rotation oder in einer anderen Seite bzw. einem anderen Dialog aktualisiert sich das Padding nativ.

#Werte

Wert Beispiel Bedeutung
env(name) paddingTop: "env(safe-area-inset-top)" Der Inset.
env(name, <length>) env(safe-area-inset-top, 20px) Der Inset; die Längenangabe dient als Ersatz für einen unbekannten Namen, wie in CSS.
calc(env(name) + <length>) calc(env(safe-area-inset-bottom) + 16px) Der Inset plus eine Länge, für Inhalte, die ihren eigenen Abstand behalten.
calc(<length> + env(name)) calc(16px + env(safe-area-inset-left)) Dasselbe.

Sie gelten für padding, paddingHorizontal, paddingVertical, paddingTop, paddingRight, paddingBottom, paddingLeft und dieselben margin-Schlüssel. In Tailwind sind sie beliebige Werte der Utilities p, px, py, pt, pr, pb, pl und m (Leerzeichen dürfen weggelassen werden): pt-[env(safe-area-inset-top)], pb-[calc(env(safe-area-inset-bottom)+16px)]. Der TypeScript-Typ dieser Werte ist EnvValue. Siehe die Style-Referenz.

In Rust setzen die Styled-Methoden sie: .p_safe_area(), .px_safe_area(), .py_safe_area(), .pt_safe_area(), .pr_safe_area(), .pb_safe_area(), .pl_safe_area(), dieselben m-Methoden sowie .pt_safe_area_plus(px(16.0)) (und pr, pb, pl, mt, mr, mb, ml) für einen Inset plus eine Länge.

#Die Kanten auswählen

Rücke nur die Kanten ein, die es brauchen, zum Beispiel wenn eine Tab-Leiste oder eine eigene untere Leiste den unteren Inset übernimmt:

view()
    .flex_1()
    .pt_safe_area()
    .px_safe_area()

#Wann man es verwendet

Eine NavPage mit sichtbarem, opakem Header platziert ihren Inhalt bereits unterhalb des Headers, daher ist der obere Inset dort 0. Die Safe-Area-Insets sind gedacht für:

  • die Root einer App ohne Navigationsstack,
  • eine Seite mit headerVisible={false} oder headerTransparent,
  • den Inhalt eines Vollbild-Dialog,
  • eigene Leisten an den Rändern des Bildschirms.

#safeArea und safeAreaInsets

Wenn du die Zahlen im Code brauchst, lies sie direkt aus. Beide TSX-Accessoren stammen aus nsh, geben ein Edges-Objekt zurück ({ top, right, bottom, left } in Punkten) und sind reaktiv: Code, der sie liest, wird erneut ausgeführt, wenn sie sich ändern, zum Beispiel bei Rotation.

TSX Rust Rückgabe
safeArea() safe_area() Der Safe Area der Surface: die Bereiche, die frei von System-UI sind (Notch, Home-Indikator, Titelleiste).
safeAreaInsets() cx.insets() Die Insets dort, wo die Komponente gerendert wird: die der nächsten Seite (unterhalb ihres Headers), die des nächsten Dialogs oder sonst der Safe Area der Surface. Das sind die Werte der safe-area-inset-*-Variablen. In TSX rufst du sie in einer Komponente auf; sie gibt einen Accessor zurück. Eine Rust-Komponente deiner Crate liest sie mit cx.insets().
use nsh_core::prelude::*;

fn bottom_bar() -> impl IntoElement {
    view()
        .flex_row()
        .p_2()
        .pb(move || if safe_area().bottom > Points::ZERO { px(4.0) } else { px(8.0) })
        .child(text("Bottom bar"))
}

Für Padding um den Inset selbst ist env() die einfachere Form: Es aktualisiert sich nativ, ohne dass Code ausgeführt wird.

#Hinweise zu den Plattformen

  • iOS: Die Insets stammen vom Safe Area von UIKit: der Statusleiste, der Notch bzw. der Dynamic Island und dem Home-Indikator.
  • Android: NSH meldet die Teile der System-Bars und des Display-Cutouts, die die NSH-View überlappen. Wenn NSH unterhalb einer nativen Toolbar eingebettet ist, beträgt der obere Inset der View 0.
  • Mac Catalyst: Der obere Inset ist die Titelleiste des Fensters.
  • Desktops (macOS, Windows, Linux, GPUI) und das Web: Der Inhalt einer Seite beginnt unterhalb ihres header, und ein Dialog gibt seinem Inhalt eigene Insets, wie auf den Smartphones.

#Siehe auch

  • Keyboard: env(keyboard-inset-height), Inhalte, die oberhalb der Software-Tastatur bleiben.
  • Device and environment: der Safe Area unter den übrigen Werten des Geräts.