本頁由機器翻譯,可能有錯誤。 閱讀英文原文

文件

圖片與資源

圖片元素(Rust 中為 image(..),TSX 中為 <Image>)可顯示點陣圖或 SVG 向量圖:可以是隨應用程式一併發佈的檔案,也可以是從 URL 下載的圖片。圖片會在主執行緒之外讀取與解碼,並降採樣至檢視所顯示的尺寸後快取;主執行緒只負責顯示解碼後的結果。

const LOGO: ImageAsset = asset!("logo@2x.png");

image(LOGO)
image("https://picsum.photos/seed/nsh/600/400").w_full().h(px(200.0)).rounded(px(12.0))

元素的 props 列於 image。

#應用程式內的圖片

Rust crate 會將檔案放在其 assets/ 目錄中:asset!("logo@2x.png")(相對於 assets/ 的路徑)是一個 ImageAsset、{ url, size, scales },於 crate 編譯時讀取,而 crate 的 #[native_module] 函式中的 registry.assets(assets!()) 會將該目錄的每個檔案隨應用程式一併發佈。在 TSX 中,像匯入模組一樣匯入圖片檔案:匯入會得到一個 ImageAsset、{ url, size, scales? },並由 bundler 將檔案隨應用程式一併發佈:

const LOGO: ImageAsset = asset!("logo@2x.png");

// LOGO.url: "asset:/@my-app/logo@2x.png", LOGO.size: 64 × 64 points

#[native_module("app/native")]
fn native(registry: &mut Registry) {
    registry.assets(assets!());
}

支援的檔案:.png、.jpg、.jpeg、.gif、.webp、.heic 與 .svg(SVG 圖片)。

尺寸以點(point)為單位。 檔名中的 @2x 或 @3x 後綴表示該檔案每點有 2 或 3 個像素,因此 128×128 像素的 logo@2x.png 是一個 64×64 點的資源。沒有後綴時,一個像素即為一點。帶有後綴的檔名只會顯示該檔案;不帶後綴的檔名在有像素密度變體時,則會顯示其變體。

自然尺寸。 當樣式未指定寬度或高度時,來自資源的圖片會以其自然尺寸進行版面配置,因此 image(LOGO) 與 <Image src={logo} /> 不需要樣式。若要以不同尺寸顯示,可設定單一維度加上長寬比,或同時設定兩個維度。

在 Rust 中,檔案遺失或損毀會使建置失敗,而檔案變更會使 crate 重新建置。在 TSX 中,資源會相對於匯入它的模組解析,其 url 則相對於應用程式根目錄(進入點所在的目錄)。nsh build 會將它們複製到 out/assets/,nsh package 會將它們放入應用程式中,而在開發期間則由開發伺服器提供。

#像素密度

檔案名稱不加後綴,並將較高密度的版本放在它旁邊,應用程式就會載入符合螢幕的版本:

assets/
├── logo.png       64×64 pixels
├── logo@2x.png    128×128
└── logo@3x.png    192×192
const LOGO: ImageAsset = asset!("logo.png");

// LOGO.size: 64 × 64 points, LOGO.scales: [1.0, 2.0, 3.0]
  • asset! 與 bundler 會為未帶後綴命名的圖片尋找 @1.5x、@2x、@3x 與 @4x 同層檔案。scales 會列出這些檔案,其中 1 代表原名檔案;沒有同層檔案的圖片沒有縮放倍率(在 TSX 中匯入為 { url, size })。bundler 會發佈它找到的同層檔案,而 assets!() 會發佈整個目錄。
  • 圖片會載入不小於螢幕像素比的最小倍率,若無則載入最大的倍率,與 React Native 的選擇方式相同:1× 螢幕載入 logo.png,2× 螢幕載入 logo@2x.png,3× 手機(以及 2.625)載入 logo@3x.png。在網頁上只會下載該檔案。
  • 當像素比改變時(視窗移到另一個螢幕、瀏覽器縮放),選擇結果改變的圖片會重新載入,並觸發其載入事件。它們的尺寸仍維持原名檔案的尺寸(以點為單位)。
  • 若變體的像素數不等於原名檔案尺寸乘以其倍率,在 TSX 中會產生建置警告,在 Rust 中則會使建置失敗。
  • 以密度後綴命名的檔案(./logo@3x.png、asset!("logo@3x.png"))僅代表該檔案本身。
  • SVG 圖片會依任何比例為螢幕繪製,因此不需要發佈變體。

只用一個檔案也可以:以你需要的最高密度匯出(@3x 可在每支手機上獲得最清晰的結果)並加上其後綴命名;圖片會在解碼時降採樣至螢幕大小。變體則讓每個螢幕只下載、解碼並保留符合自己的檔案。

#SVG 圖片

像匯入任何圖片一樣匯入 .svg 檔案。其以點為單位的尺寸取自 width 與 height(或其中之一,另一個由 viewBox 的長寬比推得),否則取自 viewBox:SVG 的一個 CSS 像素即為一點,而 @2x 後綴不會改變 SVG 檔案。

import logo from "./assets/logo.svg"; // <svg width="120" height="40" …>
import star from "./assets/star.svg"; // <svg viewBox="0 0 24 24" …>

logo; // { url: "asset:/assets/logo.svg", size: { width: 120, height: 40 } }

<Image src={logo} />
<Image src={star} style={{ width: 36, height: 36, tintColor: "#0969da" }} />

SVG 在顯示的尺寸下都會保持清晰。瀏覽器以向量方式繪製;iOS、macOS 與 Android 在圖片載入時,依檢視所顯示的尺寸(以像素計)繪製(如同它們對點陣圖進行降採樣;之後放大的檢視會縮放該繪製結果,直到圖片重新載入);Windows、Linux 與 GPUI 則以每點四個像素繪製一次(每邊最多 2048 像素),再縮放至各個檢視。其餘行為與點陣圖相同:objectFit、tintColor(透過 SVG 的不透明形狀,因此單色圖示可跟隨主題)、濾鏡、blurRadius、佔位圖、載入事件、Image.getSize 與 Image.prefetch。onLoad 與 Image.getSize 會以整數點回報 SVG 的尺寸;圖片則以其精確尺寸進行版面配置。遠端 SVG 檔案的載入方式相同,經過相同的下載與能力(capability)檢查;NSH 依內容辨識 SVG(也包括以 gzip 壓縮的,.svgz),與其 URL 結尾為何無關。

原生平台能繪製的內容。 iOS、macOS、Android、Windows、Linux 與 GPUI 使用同一個渲染器(resvg)繪製 SVG,因此圖形在各平台上看起來一致:形狀與路徑、填色與描邊、漸層與圖樣、裁切路徑與遮罩、不透明度、濾鏡與變形。文字(<text>)、動畫(SMIL 與 CSS)、內嵌圖片(<image>)與 HTML(<foreignObject>)會在瀏覽器中顯示;原生平台則以靜態方式繪製 SVG 的其餘部分,而 nsh build 會指出使用這些功能的檔案。請將文字匯出為外框(路徑),以便在各處得到相同的結果。

自成一體的圖像。 在原生平台上,SVG 只會繪製它自身包含的內容:它所參照的檔案、URL、data: 圖片與其他文件都會被省略。原生平台繪製 SVG(包括遠端 SVG)時,會受到限制以控制記憶體用量:文件最大 16 MiB(壓縮文件以解壓縮後的大小計算)、繪製結果每邊最多 8192 像素且總計最多 4096 × 4096 像素(64 MiB;較大的會以較小尺寸繪製),而圖層(帶有不透明度、遮罩、裁切路徑與濾鏡的群組,渲染器會將它們分開繪製)同時最多 256 MiB。超過文件或圖層上限的 SVG 會載入失敗,並回報 onError。

網頁上回報的尺寸。 瀏覽器會自行測量 SVG,無論是匯入的還是遠端的:當 SVG 具有 width 與 height 時,onLoad 與 Image.getSize 會回報它們,否則回報瀏覽器給定的尺寸,瀏覽器通常由預設的 300 × 150 與 viewBox 的長寬比推得。匯入的 SVG 在每個平台上都以 bundler 提供的尺寸進行版面配置。請為 SVG 設定 width 與 height,以便在各處得到相同的回報尺寸。

#遠端圖片

為元素提供 URL:在 Rust 中是字串(會變動的則用閉包),在 TSX 中是 URL 字串:

image(move || user.with(|u| u.avatar_url.clone()))
    .size(px(48.0))
    .rounded(px(24.0))
    .bg(rgb(0xeaeef2))
  • 請為遠端圖片指定尺寸。 它們的尺寸在載入之前是未知的,因此若沒有寬度與高度(或其中之一加上長寬比),它們的高度會塌縮為零。
  • 圖片載入期間會顯示背景色,或使用佔位圖(見下文)。
  • 下載是共用的:多個顯示相同 URL 的檢視只會下載一次,即使它們以不同尺寸解碼也一樣。不再有人等待的下載會被取消。
  • 解碼後的圖片會快取在記憶體中(快取會在記憶體壓力下被清除),以 URL、解碼尺寸與模糊半徑作為索引鍵。
  • 當應用程式宣告了能力時,圖片的主機必須在 network 下被允許;被拒絕的圖片會以其 error 事件失敗。

#保存於磁碟

下載的圖片也會依伺服器送出的原樣保存在磁碟上,因此重新開啟的應用程式(滿是頭像與照片的對話、動態消息)無需下載就能顯示先前顯示過的圖片,離線時亦然。應用程式不需要任何設定:在 iOS、Android、macOS、Windows、Linux 與 GPUI 上,每個使用 http:// 或 https:// URL 的圖片都會經過此快取;在瀏覽器中,則由瀏覽器自身的 HTTP 快取達到相同效果。

快取遵循伺服器的 HTTP 快取標頭:

  • 除非回應指定 Cache-Control: no-store,否則圖片會被保留。
  • 在新鮮期間內,無需發出請求即可使用:依 max-age;否則至 Expires;否則為自 Last-Modified 起算時間的十分之一(最多一週)。
  • 之後會向伺服器檢查(If-None-Match 搭配其 ETag,If-Modified-Since 搭配其 Last-Modified):未變更的圖片(304)無需下載即可再次使用,已變更的則會取代舊的。
  • 當請求失敗(沒有網路)或伺服器發生錯誤(5xx)時,會顯示保留的副本,除非回應指定了 must-revalidate 或 no-cache。

每個應用程式有自己的 256 MiB 快取;超過後,最久未使用的圖片會最先被丟棄。同一個應用程式的多個檢視、介面(surface)與處理程序可以安全地共用它。嵌入式應用程式可選擇它的位置與大小,或將它關閉:

// macOS, Windows, Linux and GPUI: the AppView's options (or AppView::set_cache).
let mut cache = CacheOptions::at(CacheLocation::Dir(caches.join("nsh")));  // each app in <dir>/<app id>
cache.images = CachePolicy::MaxSize(ByteSize::mib(64));                     // CachePolicy::Off: none
let view = AppView::new(mtm, AppOptions { cache, ..AppOptions::default() });

CacheLocation::Off(.off、noDiskCache())不會在磁碟上保存任何內容。

預設情況下,快取位於系統的快取目錄:iOS 上為 Library/Caches/nsh,Android 上為快取目錄中的 nsh,macOS 上為 ~/Library/Caches,Windows 上為 %LOCALAPPDATA%,Linux 上為 $XDG_CACHE_HOME(~/.cache),每個應用程式在其中各有自己的 <app id>/images。系統在需要空間時可能會清除這些目錄。

#縮放模式

objectFit 樣式,即 CSS 的 object-fit(Rust 中為 .object_fit(..)),決定圖片如何填滿形狀不同的方框:

值結果
"cover"(ResizeMode::Cover,預設)填滿方框,保持長寬比,裁切溢出的部分
"contain"(ResizeMode::Contain)在方框內完整顯示,保持長寬比,留下空白帶
"fill"(ResizeMode::Stretch)精確填滿方框,圖片會變形
"none"(ResizeMode::Center)自然尺寸,置於 objectPosition(預設置中)
"scale-down"(ResizeMode::ScaleDown)自然尺寸,較大時縮小以適應
image(photo).object_fit(ResizeMode::Contain).size(px(140.0)).bg(rgb(0xeaeef2))

Tailwind 類別 object-cover、object-contain、object-fill、object-none 與 object-scale-down 設定的是相同的樣式。

#圖片位置

調整後尺寸的圖片在方框中的位置由 objectPosition 樣式決定(Rust 中為 .object_position(..) 與 .object_top() 等),即 CSS 的 object-position:預設為中心。被覆蓋(cover)的圖片會圍繞該位置裁切,被包含(contain)的圖片則在空白帶內移動。它接受關鍵字("top"、"left bottom")、百分比與長度("25% 75%"、"10px 0"),以及帶偏移量的邊緣("right 10px bottom 20%")。聊天氣泡中的直式照片可以這樣顯示其頂部:

image(photo).object_fit(ResizeMode::Cover).object_top().w(px(220.0)).h(px(160.0)).rounded(px(16.0))

Tailwind 類別即 Tailwind 本身的類別:object-top、object-bottom、object-left、object-right、object-center、object-top-left、object-top-right、object-bottom-left、object-bottom-right,以及任意值(object-[25%_75%])。

#圖片樣式

圖片即是檢視:它們可使用圓角(也可逐角設定)、邊框、陰影、不透明度與變形。圓角會裁切圖片。

image(avatar).size(px(90.0)).rounded(px(45.0)).border(px(3.0)).border_color(rgb(0x8250df))
  • 著色(Rust 中為 .tint_color(..),TSX 中為 tintColor 樣式)會重新為圖片的不透明像素上色:適用於跟隨主題的單色圖示。
  • 濾鏡(.grayscale(..)、.sepia(..)、.blur(..);TSX 中為 filter)適用於兩個平台上的圖片(濾鏡)。
image(ICON).size(px(24.0)).tint_color(AdaptiveColor::rgb(0x0969da, 0x58a6ff))

#佔位圖、淡入與模糊

PropRust說明
placeholder.placeholder(..)在 src 載入完成前顯示的圖片,可以是 URL 或資源;最好使用本機資源
fadeDuration.fade_duration(ms)載入完成的圖片淡入所需的毫秒數。iOS 預設為 0,Android 預設為 300
blurRadius.blur_radius(px(..))以此半徑(以點為單位)模糊圖片,於解碼時套用一次
image(move || format!("https://picsum.photos/seed/{}/300/300", id.get()))
    .placeholder(PLACEHOLDER)
    .fade_duration(250.0)
    .size(px(100.0))
    .rounded(px(12.0))
image(cover).blur_radius(px(16.0)).absolute().inset_0()

#動畫圖片

動畫 GIF、WebP 與 APNG 檔案會自動播放,無論是資源還是來自 URL。它們可使用相同的樣式,包括 objectFit;在 iOS 上,著色的動畫圖片會顯示其第一個影格。

#載入事件

事件Rust負載時機
onLoadStart.on_load_start(..)開始載入
onProgress.on_progress(..){ loaded, total }(以位元組計,total 未知時為 0)下載期間,最多每 100 毫秒一次
onLoad.on_load(..){ size }圖片已就緒;來源的尺寸(以像素計;SVG 則以點計,在網頁上依瀏覽器的測量結果)
onError.on_error(..){ message }(Rust 中為 &str)載入或解碼失敗
onLoadEnd.on_load_end(..)在 onLoad 或 onError 之後
let status = signal(SharedString::from("loading…"));

image(url)
    .on_load(move |size: &Size<DevicePixels>| status.set(format!("{}×{}", size.width.0, size.height.0).into()))
    .on_error(move |message: &str| status.set(message.to_owned().into()))
    .w(px(90.0))
    .h(px(60.0))

load 事件回報的是來源圖片的尺寸,而非解碼時使用的尺寸。

具有像素密度變體的圖片會載入其中一個檔案,而 load 事件回報的是該檔案的像素數:在 3× 螢幕上,logo@3x.png 為 192×192,而 Image.getSize(logo) 則解析為匯入檔案的 64×64。當像素比改變且圖片載入另一個變體時,該次載入也會回報其事件:onLoadStart,接著是帶有新檔案尺寸的 onLoad(或 onError),然後是 onLoadEnd。

#圖片的尺寸與預先載入

不在畫面上的圖片可以事先測量並提前載入:Rust 中使用 image_size(..) 和 prefetch_image(..),TSX 中則使用 Image.getSize 和 Image.prefetch(位於渲染該元素的同一個 Image 上):

spawn(async move {
    // Ok(ImageSize { width, height }), in pixels.
    let size = image_size("https://example.com/photo.jpg").await;
    // Ok(()) once the bytes are in memory.
    let loaded = prefetch_image("https://example.com/next.jpg").await;
});
RustTSX傳回值
image_size(src)Image.getSize(src)圖片的尺寸,單位為像素(SVG 則以點為單位,在網頁上則以瀏覽器的測量結果為準);無法載入時會傳回錯誤
prefetch_image(src)Image.prefetch(src)圖片的位元組載入完成後不傳回任何值;無法載入時會傳回錯誤

在 TSX 中,src 可以是 URL 字串或匯入的圖片。對於具有變體的匯入圖片,兩者都會載入視圖在此螢幕上所載入的變體,而 getSize 傳回的是匯入檔案本身的尺寸。兩者都會保留已下載的位元組,供之後顯示該圖片的視圖使用,因此在顯示前已預先載入的圖片無需下載即可出現。

常見的用法是依長寬比調整遠端圖片的大小:

fn remote_image(url: &'static str) -> impl IntoElement {
    let ratio = signal(1.0_f32);
    spawn(async move {
        if let Ok(size) = image_size(url).await {
            ratio.set(size.width.0 as f32 / size.height.0 as f32);
        }
    });
    image(url).w_full().aspect_ratio(ratio)
}

#其他資源

檔案Rust(asset!)TSX 匯入頁面
.ttf、.otf、.woff、.woff2帶有字型家族名稱的 FontAsset字型的家族名稱字型
.json 及其他檔案帶有其 URI 的 FileAsset.json:解析後的值,已打包進指令碼中專案結構

在兩種方式中,字型都會在第一個影格之前完成註冊。

#在執行階段讀取檔案

fetch 會依應用程式隨附檔案的 asset:/ URL 來讀取該檔案,在 TSX 與 Rust 中皆適用,且支援所有平台。匯入的圖片帶有其 URL(url),Rust crate 的檔案則可透過 asset!(FileAsset::uri)和 assets!() 取得其 URL:

const GREETING: FileAsset = asset!("locales/en.txt");

#[component]
fn Greeting() -> impl IntoElement {
    let greeting = signal(String::new());
    spawn(async move {
        match fetch(GREETING.uri).await {
            Ok(response) => greeting.set(response.text()),
            Err(error) => greeting.set(error),
        }
    });
    text(move || greeting.get())
}

引擎會在自己的執行緒之外,從應用程式的檔案中讀取該檔案(在 nsh run 期間則從開發伺服器讀取),不需要任何權限:這些檔案屬於應用程式自身。網頁應用程式會從其提供資源的位置下載頁面尚未預先載入的內容。回應為 200,內容包含檔案的位元組、其 Content-Length,以及依副檔名推得的 Content-Type(image/png、application/json 等),而無論檔案是以何種方式取得,其 url 都是 asset:/ URL;若檔案並非應用程式所隨附,則會以指出該檔案的網路錯誤拒絕。URL 以檔案在應用程式檔案內的路徑來指定檔案,而請求以 GET(或 HEAD)讀取:路徑超出這些檔案範圍的 URL(..、絕對路徑)以及使用其他方法的請求,都會以相同方式被拒絕,且在讀取或下載任何內容之前就會拒絕。

#模組自身的 URL

在 TSX 中,import.meta.url 是撰寫該程式碼的模組的 URL,因此 new URL("./data.json", import.meta.url) 會指向與它相鄰的檔案。本機或已打包的桌面應用程式,其模組具有位於應用程式目錄中的 file: URL,fs/promises 會在 ${pluginDir} 授權下讀取這些 URL;網頁應用程式的模組 URL 則是頁面的 URL;已打包的行動應用程式的模組 URL 是 asset:/ URL,與匯入圖片的 URL 相同。若要隨應用程式打包圖片,請以匯入的方式引入:以這種方式建立的 URL 只是指向該檔案,並不會將它打包。

點陣圖也可以使用 draw_image / drawImage 繪製在畫布上(畫布),而漸層可以取代許多裝飾性圖片(樣式)。

#相關內容

  • image 元素:所有屬性,包括 getSize 和 prefetch。
  • 清單:包含大量圖片的長清單會回收其視圖。