Esta página foi traduzida automaticamente e pode conter erros. Ler o original em inglês

Elementos

WebView

Uma web view mostra uma página da web, ou HTML que você fornece, dentro do app: WKWebView no iOS e no Mac Catalyst, android.webkit.WebView no Android. Como o NSH renderiza com views nativas, uma web view se encaixa no layout como qualquer outra view, na ordem das views ao redor. Em Rust, você cria uma com web_view(url) ou web_view_html(html, base_url); em TSX, com <WebView> de 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())),
        )
}

Em TSX, uma web view recebe src ou html (veja a WebView API). O guia de web view e vídeo aborda padrões de mensagens e capacidades.

#Props

Uma web view recebe as props de uma view (ela não tem filhos) e mais:

Prop Rust Tipo Padrão Descrição
src web_view(url) string A URL da página a ser exibida.
html web_view_html(html, base_url), .html(html, base_url) string HTML inline, exibido no lugar de src.
baseUrl base_url de web_view_html / .html string Em relação a que os links relativos de html são resolvidos.
injectedJavaScript .injected_java_script(..) string Executa depois que cada página é carregada.
injectedJavaScriptBeforeContentLoaded .injected_java_script_before_content(..) string Executa antes dos scripts da própria página.
userAgent .user_agent(..) string A string de user agent do navegador.
javaScriptEnabled .java_script_enabled(..) boolean true As páginas executam scripts.
scrollable .scrollable(..) boolean true O usuário pode rolar a página.
Chave ios Rust Tipo Descrição
allowsBackForwardNavigationGestures .back_forward_gestures(..) boolean Deslizar a partir das bordas para voltar e avançar.
Chave desktop Rust Tipo Descrição
allowsBackForwardNavigationGestures .back_forward_gestures(..) boolean macOS: deslizar com dois dedos no trackpad para voltar e avançar; Windows: deslizar em uma tela sensível ao toque; Linux: deslizar no touchpad.

#Eventos

Evento Rust Payload Quando
onLoadStart .on_load_start WebViewNavigation Uma página começou a carregar.
onLoad .on_load WebViewNavigation Uma página terminou de carregar.
onError .on_error { message, code? } (&str, a mensagem) O carregamento falhou; code é "ERR_ACCESS_DENIED" para uma página ou html que as capacidades do app não concedem.
onNavigationStateChange .on_navigation_state_change WebViewNavigation A URL, o título, o estado de carregamento ou o histórico mudou.
onMessage .on_message { data: string } (&str) A página chamou window.nsh.postMessage(data).
onDownload WebViewDownload Um download começou, terminou ou falhou (macOS).

WebViewNavigation é { url, title, loading, canGoBack, canGoForward }; em Rust, WebNavigation com url, title, loading, can_go_back e can_go_forward.

WebViewDownload é { state, id, url, filename, path, bytes?, message? }: state é "started", "finished" (com o bytes do arquivo) ou "failed" (com seu message). Com um listener onDownload, o que a página baixa (um anexo, um tipo que a web view não exibe, um link download) é salvo no diretório de dados do app como downloads/<id>/<filename>, para onde path aponta, e o app o lê com fs/promises.

No macOS, adicionar o listener permite que qualquer página carregada e permitida pela capacidade webview salve downloads automaticamente. O app é responsável pelo seu orçamento de armazenamento e por remover os arquivos concluídos de downloads/ quando terminar de usá-los. Remover a web view cancela os downloads em andamento e remove seus diretórios parciais. Os downloads são reportados por onDownload; converter uma navegação em download mantém onError para falhas reais de navegação. Nos nomes de arquivo, caracteres de controle e controles de formatação bidirecional são removidos, e nomes longos são encurtados para 200 bytes, preservando uma extensão que caiba.

#Métodos do ref

Método Rust Descrição
reload() reload() Recarrega a página.
goBack() / goForward() go_back() / go_forward() Navega pelo histórico da página.
stopLoading() stop_loading() Interrompe o carregamento.
injectJavaScript(script) inject_java_script(script) Executa JavaScript na página.
postMessage(data) post_message(data) Envia uma string para a página, que recebe um evento message em window.

#Mensagens

O app e a página trocam strings. A página envia com window.nsh.postMessage(data); o app envia com o postMessage(data) do ref (post_message(..) em Rust). Envie JSON para dados estruturados.

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")),
        )
}

#Capacidades

Quando o app declara capacidades em nsh.json, uma web view exibe e navega para os hosts listados em webview. Um src em outro host, ou html em um app sem webview, exibe uma view vazia; uma navegação de nível superior para outro host (um link em que o usuário toca) é cancelada nativamente. Cada caso reporta onError com code "ERR_ACCESS_DENIED".

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

#Notas de plataforma

  • O iOS carrega o WebKit na primeira vez que uma web view é criada, então isso só tem custo para o app quando ele exibe uma.
  • No macOS, onDownload salva downloads com o WKDownload do WebKit.
  • No Android, as chamadas postMessage da página chegam em uma thread em segundo plano e são encaminhadas ao app em ordem.
  • Para arredondar os cantos de uma web view, dê a ela um raio de canto e overflow: "hidden" (.overflow_hidden()).