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

Elemente

WebView

Eine Web View zeigt eine Webseite oder von dir übergebenes HTML innerhalb der App an: WKWebView auf iOS und Mac Catalyst, android.webkit.WebView auf Android. Da NSH mit nativen Views rendert, sitzt eine Web View wie jede andere View im Layout, in der Reihenfolge der umgebenden Views. In Rust erzeugst du sie mit web_view(url) oder web_view_html(html, base_url), in TSX mit <WebView> aus nsh.

el-web-view.tsx
el-web-view.tsxLive
import { mount, signal, Text, View, WebView, type WebViewNavigation, type WebViewRef } from "nsh";
import { platform, windowSize } from "nsh";
import { button, buttonText, ui } from "./palette";

// The page: a button that posts to the app, and a line that shows what the
// app posts back.
const page = (c: ReturnType<typeof ui>) => `<!doctype html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Inline page</title>
<style>
  body { font: 15px system-ui, sans-serif; margin: 0; padding: 16px; background: ${c.page}; color: ${c.text}; }
  button { font: inherit; font-weight: 600; padding: 10px 14px; border-radius: 10px; border: 1px solid ${c.border}; background: ${c.accent}; color: ${c.page}; }
  .muted { color: ${c.muted}; }
  #got { margin-top: 12px; }
  #box { margin-top: 12px; height: 48px; border-radius: 10px; background: ${c.border}; transition: background 0.3s; }
</style>
<p class="muted">This is HTML in a web view.</p>
<button id="post">postMessage to the app</button>
<p id="got" class="muted">Nothing from the app yet.</p>
<div id="box"></div>
<script>
  let taps = 0;
  document.getElementById("post").onclick = () =>
    window.nsh.postMessage("tap " + ++taps + " from the page");
  window.addEventListener("message", (e) => { document.getElementById("got").textContent = "App says: " + e.data; });
</script>`;

function Button(props: { label: string; onPress: () => void }) {
  return (
    <View
      onPress={props.onPress}
      {...button()}
    >
      <Text style={buttonText()}>{props.label}</Text>
    </View>
  );
}

const colors = () => [ui().accent, ui().info, ui().violet, ui().down, ui().warn];

function App() {
  const messages = signal<string[]>([]);
  const nav = signal<WebViewNavigation | null>(null);
  const sent = signal(0);
  let painted = 0;
  let web!: WebViewRef;
  const wide = () => windowSize().width >= 700;

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>web-view</Text>
      <View style={{ flex: 1, flexDirection: wide() ? "row" : "column", gap: 16 }}>
        <WebView
          ref={web}
          html={page(ui())}
          // Runs after each load: the page reports its title.
          injectedJavaScript={`window.nsh.postMessage("loaded: " + document.title)`}
          onLoad={(e) => nav.set(e)}
          onMessage={(e) => messages.set([e.data, ...messages()].slice(0, 4))}
          style={{ flex: 1, minHeight: 240, borderRadius: 14, overflow: "hidden", borderWidth: 1, borderColor: ui().border }}
        />
        <View style={{ gap: 12, width: wide() ? 320 : undefined }}>
          <Text style={{ color: ui().muted }}>
            {platform.desktop ? "Click" : "Tap"} the button in the page to post to the app; these buttons call the ref's methods.
          </Text>
          <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
            <Button
              label="postMessage"
              onPress={() => {
                sent.update((n) => n + 1);
                web.postMessage(`hello #${sent()}`);
              }}
            />
            <Button
              label="injectJavaScript"
              onPress={() => {
                const color = colors()[painted++ % colors().length];
                web.injectJavaScript(`document.getElementById("box").style.background = "${color}"`);
              }}
            />
            <Button label="reload" onPress={() => web.reload()} />
          </View>
          <View style={{ ...ui().card, borderRadius: 12, padding: 12, gap: 4 }}>
            <Text style={{ color: ui().muted, fontSize: 12 }}>onLoad</Text>
            <Text style={{ numberOfLines: 1, color: ui().text, fontFamily: "monospace", fontSize: 13 }}>
              {nav() ? `title "${nav()!.title}"` : "…"}
            </Text>
            <Text style={{ color: ui().muted, fontSize: 12, marginTop: 6 }}>onMessage, newest first</Text>
            <Each of={messages()}>
              {(m) => <Text style={{ color: ui().text, fontFamily: "monospace", fontSize: 13 }}>{m()}</Text>}
            </Each>
          </View>
        </View>
      </View>
    </View>
  );
}

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

fn browser() -> impl IntoElement {
    let title = signal(SharedString::from("Loading…"));
    view()
        .flex_1()
        .child(text(title).number_of_lines(1))
        .child(
            web_view("https://example.com")
                .flex_1()
                .on_navigation_state_change(move |e: &WebNavigation| title.set(e.title.clone().into())),
        )
}

In TSX nimmt eine Web View entweder src oder html entgegen (siehe die WebView API). Der Leitfaden zu Web View und Video behandelt Messaging-Muster und Capabilities.

#Props

Eine Web View akzeptiert die Props einer View (sie hat keine Kinder) sowie:

Prop Rust Typ Standard Beschreibung
src web_view(url) string Die URL der anzuzeigenden Seite.
html web_view_html(html, base_url), .html(html, base_url) string Inline-HTML, wird anstelle von src angezeigt.
baseUrl base_url von web_view_html / .html string Die Basis, gegen die relative Links in html aufgelöst werden.
injectedJavaScript .injected_java_script(..) string Wird nach dem Laden jeder Seite ausgeführt.
injectedJavaScriptBeforeContentLoaded .injected_java_script_before_content(..) string Wird vor den eigenen Skripten jeder Seite ausgeführt.
userAgent .user_agent(..) string Der User-Agent-String des Browsers.
javaScriptEnabled .java_script_enabled(..) boolean true Seiten führen Skripte aus.
scrollable .scrollable(..) boolean true Der Benutzer kann die Seite scrollen.
ios-Schlüssel Rust Typ Beschreibung
allowsBackForwardNavigationGestures .back_forward_gestures(..) boolean Von den Rändern wischen, um zurück und vor zu navigieren.
desktop-Schlüssel Rust Typ Beschreibung
allowsBackForwardNavigationGestures .back_forward_gestures(..) boolean macOS: mit zwei Fingern auf dem Trackpad wischen, um zurück und vor zu navigieren; Windows: auf einem Touchscreen wischen; Linux: auf dem Touchpad wischen.

#Ereignisse

Ereignis Rust Payload Wann
onLoadStart .on_load_start WebViewNavigation Eine Seite hat begonnen zu laden.
onLoad .on_load WebViewNavigation Eine Seite hat das Laden abgeschlossen.
onError .on_error { message, code? } (&str, die Meldung) Das Laden ist fehlgeschlagen; code ist "ERR_ACCESS_DENIED" für eine Seite oder html für eine, die die Capabilities der App nicht gewähren.
onNavigationStateChange .on_navigation_state_change WebViewNavigation Die URL, der Titel, der Ladezustand oder der Verlauf hat sich geändert.
onMessage .on_message { data: string } (&str) Die Seite hat window.nsh.postMessage(data) aufgerufen.
onDownload WebViewDownload Ein Download wurde gestartet, abgeschlossen oder ist fehlgeschlagen (macOS).

WebViewNavigation ist { url, title, loading, canGoBack, canGoForward }; in Rust WebNavigation mit url, title, loading, can_go_back und can_go_forward.

WebViewDownload ist { state, id, url, filename, path, bytes?, message? }: state ist "started", "finished" (mit dem bytes der Datei) oder "failed" (mit zugehörigem message). Mit einem onDownload-Listener wird das, was die Seite herunterlädt (ein Anhang, ein Typ, den die Web View nicht anzeigt, ein download-Link), im Datenverzeichnis der App als downloads/<id>/<filename> gespeichert, worauf path zeigt, und die App liest es mit fs/promises.

Unter macOS erlaubt das Hinzufügen des Listeners jeder geladenen Seite, die durch die Capability webview zugelassen ist, Downloads automatisch zu speichern. Die App ist für ihr Speicherbudget verantwortlich und dafür, abgeschlossene Dateien aus downloads/ zu entfernen, sobald sie sie nicht mehr benötigt. Das Entfernen der Web View bricht laufende Downloads ab und entfernt deren partielle Verzeichnisse. Downloads werden über onDownload gemeldet; wird eine Navigation in einen Download umgewandelt, bleibt onError tatsächlichen Navigationsfehlern vorbehalten. Aus Dateinamen werden Steuerzeichen und bidirektionale Formatierungszeichen entfernt, und lange Namen werden auf 200 Bytes gekürzt, wobei eine passende Dateiendung erhalten bleibt.

#Ref-Methoden

Methode Rust Beschreibung
reload() reload() Lädt die Seite neu.
goBack() / goForward() go_back() / go_forward() Navigiert im Verlauf der Seite.
stopLoading() stop_loading() Beendet das Laden.
injectJavaScript(script) inject_java_script(script) Führt JavaScript in der Seite aus.
postMessage(data) post_message(data) Sendet einen String an die Seite, die ein message-Ereignis auf window erhält.

#Messaging

Die App und die Seite tauschen Strings aus. Die Seite sendet mit window.nsh.postMessage(data); die App sendet mit der postMessage(data) der Ref (post_message(..) in Rust). Sende JSON für strukturierte Daten.

use nsh_core::prelude::*;

const PAGE: &str = r#"<!doctype html>
<meta name="viewport" content="width=device-width">
<button onclick="window.nsh.postMessage('hello from the page')">Post to the app</button>
<p id="got"></p>
<script>
  window.addEventListener("message", (e) => { document.getElementById("got").textContent = e.data; });
</script>"#;

fn bridge() -> impl IntoElement {
    let message = signal(String::new());
    let web = element_ref();
    view()
        .gap_3()
        .child(
            web_view_html(PAGE.to_string(), None)
                .element_ref(&web)
                .h(px(200.0))
                .on_message(move |data: &str| message.set(data.to_string())),
        )
        .child(
            text(move || format!("Post to the page · got: {}", message.get()))
                .on_press(move || web.post_message("hi from the app")),
        )
}

#Capabilities

Wenn die App Capabilities in nsh.json deklariert, zeigt eine Web View die unter webview aufgeführten Hosts an und navigiert dorthin. Ein src auf einem anderen Host oder html in einer App ohne webview zeigt eine leere View; eine Top-Level-Navigation zu einem anderen Host (ein Link, den der Benutzer antippt) wird nativ abgebrochen. Jeder dieser Fälle meldet onError mit code "ERR_ACCESS_DENIED".

{
  "capabilities": {
    "webview": { "hosts": ["example.com", "*.example.com"] }
  }
}

#Hinweise zu Plattformen

  • iOS lädt WebKit erst, wenn zum ersten Mal eine Web View erstellt wird; es kostet eine App also nur dann etwas, wenn sie eine anzeigt.
  • Unter macOS speichert onDownload Downloads mit WebKits WKDownload.
  • Unter Android treffen die postMessage-Aufrufe der Seite in einem Hintergrund-Thread ein und werden der Reihe nach an die App weitergeleitet.
  • Um die Ecken einer Web View abzurunden, gib ihr einen Eckenradius und overflow: "hidden" (.overflow_hidden()).