UNPKG

react-native-fast-image

Version:
608 lines (607 loc) • 25.1 kB
import React from 'react'; import { ColorValue, View, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native'; import { cacheControl, priority, resizeMode } from './constants'; /** How the image fits the view: see [`resizeMode`](#resizemode). */ export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat'; /** An image's load priority: see [`source.priority`](#sourcepriority). */ export type Priority = 'low' | 'normal' | 'high'; /** How fresh an image must be: see [`source.cache`](#sourcecache). */ export type Cache = 'immutable' | 'web' | 'cacheOnly'; /** A remote image to load, and how to load and cache it. */ export type Source = { /** * Remote url to load the image from. e.g. `'https://example.com/image.jpg'`. * * Also loads local files (`file://`, and on Android `content://`), images * in the app by name (e.g. `'my_image'`: in its asset catalog on iOS, a * drawable on Android), photo library images on iOS and tvOS (`ph://`, * see [Photo library images](#photo-library-images-ios-and-tvos)) and SVG * images (see [SVG images](docs/formats.md#svg-images)). */ uri?: string; /** Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`. */ headers?: { [key: string]: string; }; /** * A hint for which images to start loading first when several are * waiting: `'high'` ones before `'normal'` ones, and `'low'` ones after. * It's best effort, not an order: several images load at once, and when * each finishes depends on its size and the network. * * - `'low'`: e.g. images further down a list. * - `'normal'`: the default. * - `'high'`: e.g. the image the screen is about. * * @default 'normal' */ priority?: Priority; /** * How fresh the image must be. See * [how caching is handled](docs/how-is-caching-handled.md) for how the * options fit together. * * - `'immutable'`: loads the image once, then shows the cached copy until * its url (or `cacheKey`) changes. * - `'web'`: follows the server's HTTP cache headers, as a browser does, * checking with the server when it loads. These responses are kept in * their own HTTP cache (50 MB on each platform), which `clearDiskCache` * also clears. * - `'cacheOnly'`: only shows a cached image, without making a request. * * @default 'immutable' */ cache?: Cache; /** * The key the image is cached under, instead of its uri. Use it for urls * that change while the image stays the same, e.g. signed urls with a * token or an expiry. Use something that identifies the image, and change * it when the image changes (e.g. include a version or the time it was * updated), or the old image keeps showing. * * Not used with `cache: 'web'`, which follows the HTTP cache (keyed by * url). * * @example * ```jsx * <FastImage * source={{ * uri: signedUrl, * cacheKey: `avatar-${user.id}-${user.avatarUpdatedAt}`, * }} * /> * ``` */ cacheKey?: string; /** * Whether the decoded image is kept in the memory cache. With `false` * it's only kept on disk: a view doesn't leave it in memory once it stops * showing it, and `FastImage.preload` downloads it without decoding it. * Use it for large images that are shown once or rarely, like a * full-screen photo: a decoded photo can take tens of MB of memory. Images * shown again, like a list scrolled back, are decoded from disk again. * * @default true */ memoryCache?: boolean; /** * With several sources (`source` as an array), the image's width at this * uri, in pixels (times `scale`, if given). */ width?: number; /** * With several sources (`source` as an array), the image's height at this * uri, in pixels (times `scale`, if given). */ height?: number; /** With several sources, the scale `width` and `height` are multiplied by. */ scale?: number; }; /** How the image fades in: see [`transition`](#transition). */ export type Transition = { /** * How long the fade takes, in milliseconds; 0 means no fade. Defaults to * the platform's usual length: 300 ms on Android, 250 ms on iOS. */ duration?: number; /** * Also fades between images: a new `source` replacing an image that's * showing cross-dissolves from it. Otherwise only an image that appears * over nothing (or over `defaultSource`) fades in, and a new source * replaces the image showing at once, once it has loaded. * @default false */ betweenImages?: boolean; /** * Skips the fade for an image from a cache, so images already loaded * show at once, e.g. in a list scrolled back up or a reused row. * * - `'memory'`: skips it for images from the memory cache. * - `'all'`: skips it for images from the memory or disk cache too, so * images that download fade in, and local files and bundled images * usually only the first time (the disk cache usually keeps them too). * - `'none'`: always fades. * * Downloads, local files and bundled images (`require()`) fade in. On * Android, a `cache: 'web'` image from its HTTP cache, without a request, * counts as one from the memory cache (these images aren't kept there). * @default 'memory' */ skipOnCacheHit?: 'none' | 'memory' | 'all' | null; }; /** * `onLoad`'s event: `nativeEvent` has the image's `width` and `height`, in * pixels, and `target`, the view's React tag (missing on Android with the * legacy architecture). */ export interface OnLoadEvent { nativeEvent: { width: number; height: number; target?: number; }; } /** * `onError`'s event: `nativeEvent.error` says what went wrong, e.g. an HTTP * status code or an image that can't be decoded. */ export interface OnErrorEvent { nativeEvent: { error: string; }; } /** * A load's result, as `onLoadEnd` gets it: `ok` with the image's size, or not * `ok` with the error (as `onLoad` and `onError` get them). */ export type LoadResult = { ok: true; width: number; height: number; } | { ok: false; error: string; }; /** * `configureCache`'s changes: a number (0 for no limit), or `null` to go back * to the app's native config (Info.plist, AndroidManifest.xml) or the * platform's default. Leave one out to keep it. Changes are saved, and used on * the next launches too. Android only has `maxDiskSize`. */ export interface CacheLimits { /** * The most bytes of images kept on disk. When it's over, the least * recently used are removed (on iOS, until it's half this size). Default: * no limit on iOS, 250 MB on Android. */ maxDiskSize?: number | null; /** * iOS: the seconds an image is kept on disk after it was last used, or 0 * to keep them until `maxDiskSize` removes them. Default: 1 week. */ maxDiskAge?: number | null; /** * iOS: the most bytes of decoded images kept in memory. Default: no limit * (they're removed when the system is low on memory). */ maxMemorySize?: number | null; } /** * `configureCache`'s result: the limits in effect (0 for no limit), and the * bytes the disk cache uses now. Android only has `maxDiskSize` and * `diskSize`, and neither if the app has its own `AppGlideModule` (or * expo-image, which has one). */ export interface CacheState { maxDiskSize?: number; maxDiskAge?: number; maxMemorySize?: number; /** The bytes the disk cache uses now. */ diskSize?: number; } /** * `getCachePath`'s and `writeToCache`'s result: `ok` with the file's path, or * not `ok` with the error. */ export type CachePathResult = { ok: true; path: string; } | { ok: false; error: string; }; /** * `clearMemoryCache`'s and `clearDiskCache`'s result: `ok` once the cache is * cleared, or not `ok` with the error. */ export type ClearCacheResult = { ok: true; } | { ok: false; error: string; }; /** * `onProgress`'s event: `nativeEvent` has the bytes `loaded` and the `total`, * and `progress`, `loaded / total` from 0 to 1. */ export interface OnProgressEvent { nativeEvent: { loaded: number; total: number; progress: number; }; } export interface ImageStyle extends ViewStyle { overlayColor?: ColorValue; tintColor?: ColorValue; } export interface FastImageProps extends AccessibilityProps, ViewProps { /** * Source for the remote image to load: a `Source`, a `require()`d image, * or an array of the same image at different sizes. * * When `source` changes, the image that's showing stays until the new one * has loaded. In views that get reused for other content, such as rows in * FlashList or recyclerlistview, set `recyclingKey` so a reused row * doesn't show the previous row's image. * * **Several sizes of an image.** `source` can also be an array of the * same image at different sizes, each with its `width` and `height` in * pixels (times `scale`, if it has one). The view loads the one whose size * is closest to its own, in pixels, so a small view downloads a small * image: * * ```jsx * <FastImage * style={{ width: 120, height: 120 }} * source={[ * { uri: 'https://example.com/photo-200.jpg', width: 200, height: 200 }, * { uri: 'https://example.com/photo-800.jpg', width: 800, height: 800 }, * ]} * /> * ``` * * - The view picks once it has been laid out. One that has no size (e.g. * sized from `onLoad`) loads the largest. * - When the view's size changes so that another size fits better, it * loads that one (with its load events), and keeps showing the current * image until then, without a `transition`. * - Each size is cached separately. A `cacheKey` applies to its own entry: * give each size its own key (or none), or the sizes would replace each * other in the cache. * - `FastImage.preload`, `getCachePath` and `writeToCache` take one size: * pass the one a view will show. An array fails with * `{ ok: false, error }`. * - An array of one is the same as that source. */ source?: Source | Source[] | ImageRequireSource; /** * An asset loaded with `require(...)`, shown while the first image loads, * and if an image fails to load. When `source` changes, the previous image * shows while the new one loads instead (see `source` and * `recyclingKey`). * * On Android, `defaultSource` doesn't show in debug builds: there the dev * server serves `require()`d images, and `defaultSource` is only loaded * from the app's resources. */ defaultSource?: ImageRequireSource; /** * How the image fills the view. * * - `'contain'`: scales it uniformly (keeping its aspect ratio) so all of * it fits in the view (minus padding). * - `'cover'`: scales it uniformly (keeping its aspect ratio) so it covers * the view (minus padding), cropping what doesn't fit. * - `'stretch'`: scales its width and height separately to fill the view, * which can change its aspect ratio. * - `'center'`: centers it at its own size, scaled down uniformly to fit * if it's larger than the view. * - `'repeat'`: repeats it to cover the view, from its top-left corner, at * the image's own size in pixels (a bundled image at its size in * points), scaled down to fit if it's larger than the view. An animated * image repeats its first frame, and `defaultSource` repeats too. * * @default 'cover' */ resizeMode?: ResizeMode; /** * If true, the image is shown with React Native's `Image` instead, styled * and laid out the same way. FastImage's own features, such as its * caching options, `priority` and `transition`, don't apply. */ fallback?: boolean; /** * For views that get reused for other content, such as rows in FlashList * or recyclerlistview. Set it to something that identifies the content, * e.g. the item's id. When it changes, the image is cleared right away (to * `defaultSource`, or blank) instead of staying until the new one has * loaded. Unlike changing `key`, this keeps the view, which is what list * recycling is for. * * @example * ```jsx * <FastImage recyclingKey={item.id} source={{ uri: item.imageUrl }} /> * ``` */ recyclingKey?: string | null; /** * How many times an animated image (GIF, animated WebP, APNG, or animated * AVIF) plays: * * - Not set: as many times as the file says (like a browser). * - `true`: forever. * - `false`: once. * - A number: that many times. * * Changing it restarts the animation. */ loop?: boolean | number; /** * How the image is filtered when it's drawn smaller or larger than its * size (like CSS's `image-rendering`): * * - `'auto'`: the platform's usual filtering. * - `'smooth'`: iOS only. Keeps a large image drawn much smaller than its * size (e.g. a big photo as a thumbnail, or fine lines and text) from * looking jagged or noisy. Such an image is already decoded at about the * view's size by default (see `downsample`), so this is for images shown * at less than half their size with `downsample={false}`, or a little * smaller than their size. **It uses more memory:** the image is also * kept at smaller sizes for drawing, about a third more than the decoded * image. On Android it's the same as `'auto'` (images are always decoded * at about the view's size there). * - `'pixelated'`: sharp pixels, without smoothing, e.g. for pixel art * drawn larger than its size. On Android, animated images are still * smoothed. * * @default 'auto' */ imageRendering?: 'auto' | 'smooth' | 'pixelated'; /** * Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on * the frame it's showing; `false` plays it again from there * (on Android, an animated WebP or AVIF plays again from its first frame: * Android can't resume one). Each image animates on its own, so pausing * one doesn't pause others showing the same file. */ paused?: boolean; /** * Fades the image in when it loads. `true` uses the platform's usual * fade, a number is the duration in milliseconds, or pass a `Transition`. * * Downloads, local files (`file://`, `content://`) and bundled images * (`require()`) fade in. In lists that reuse views (e.g. FlashList), set * `recyclingKey`, so a reused view starts empty and its image fades in, * instead of showing the previous item's image until it loads. * * @default false * @example * ```jsx * <FastImage source={{ uri }} transition /> * <FastImage source={{ uri }} transition={500} /> * <FastImage source={{ uri }} transition={{ betweenImages: true, skipOnCacheHit: 'none' }} /> * ``` */ transition?: boolean | number | Transition | null; /** * Decodes a large image at about the size it's shown at, instead of at * full size, so it takes much less memory. * * - `true`: an image at least twice the size its view needs is decoded * at about the view's size. If the view grows, the image is decoded * again for its new size (from the disk cache). * - `false`: images are decoded at full size, e.g. for an image that's * zoomed in on with a transform (a pinch-to-zoom viewer), which would * otherwise show the smaller copy enlarged. * * It doesn't change `onLoad`'s width and height (the image's own size) or * the cached file. Needs SDWebImage 5.19.7 or later: before 5.19 images * are decoded at full size, and 5.19.0 to 5.19.6 show photos stored * sideways with an EXIF orientation (most phone photos) sideways. Photo * library images are always decoded this way, and on Android images are * always decoded at about the view's size (but Android 16 and later * decode animated WebP at full size, and scale it as they draw it). * * If you control the images, serve them at the size they're shown * (resized on your server or by an image CDN), which also saves * bandwidth. * * @platform ios * @default true */ downsample?: boolean; /** * Blurs the image by this radius, in points (the same radius looks about * the same on iOS and Android). `0` is no blur. * * It's for still images, or a radius that changes now and then (e.g. * blurring a photo behind a sheet). Each change blurs the image again on * the CPU, so don't animate it. * * - Only the loaded image is blurred, not `defaultSource`. * - An animated image (GIF, animated WebP, APNG or AVIF) shows its first frame, * blurred, and doesn't animate. * - With `tintColor`, the blurred image is tinted. * - The image is blurred at about the size it's shown at, off the main * thread. The cached file stays the original image, so `getCachePath` * and other views of it aren't affected. * - Changing it blurs the image that's showing again, without sending the * load events again. * * To animate a blur, or to blur an animated image, use React Native's * `filter` style instead, which the GPU draws: * `style={{ filter: [{ blur: 6 }] }}`. It needs the New Architecture. * React Native's docs list `blur` for Android 12+ only; on iOS it's behind * an experimental React Native feature flag (`enableSwiftUIBasedFilters`, * SwiftUI-based filters). * * @default 0 */ blurRadius?: number; /** Called when the image starts to load. */ onLoadStart?(): void; /** * Called while the image downloads, with the bytes `loaded` so far, the * `total`, and `progress` (`loaded / total`, from 0 to 1; the last event * has 1). Not called while the total is unknown (a response without a * `Content-Length`). * * @example * ```jsx * onProgress={e => console.log(e.nativeEvent.progress)} * ``` */ onProgress?(event: OnProgressEvent): void; /** * Called on a successful image fetch. Called with the width and height of * the image itself, not of the view (on iOS, in points: an `@2x` asset * reports half its pixel size). * * @example * ```jsx * onLoad={e => console.log(e.nativeEvent.width, e.nativeEvent.height)} * ``` */ onLoad?(event: OnLoadEvent): void; /** * Called on an image fetching error, with a message describing it (e.g. * the HTTP status code). On iOS and Android, a download that gets nothing * from the server for 15 seconds fails too (on Android, unless the app's * OkHttp client has timeouts of its own). * * @example * ```jsx * onError={e => console.log(e.nativeEvent.error)} * ``` */ onError?(event: OnErrorEvent): void; /** * Called when the image finishes loading, whether it was successful or an * error, with the result: `{ ok: true, width, height }` (the image's size, * as `onLoad` gets it) or `{ ok: false, error }` (as `onError` gets it). * TypeScript makes you check `ok` before reading the size or the error. * * @example * ```jsx * <FastImage * source={{ uri }} * onLoadEnd={(result) => { * if (result.ok) setAspectRatio(result.width / result.height) * else setFailed(result.error) * }} * /> * ``` */ onLoadEnd?(result: LoadResult): void; /** * The image's style: View's style props (`borderRadius` clips the image), * and `tintColor`, as with React Native's `Image` (the `tintColor` prop * wins). */ style?: StyleProp<ImageStyle>; /** * If supplied, changes the color of all the non-transparent pixels to the * given color. */ tintColor?: ColorValue; /** * Render children within the image. * * @deprecated In the next major version, `FastImage` won't render * children: use `FastImageBackground`. */ children?: React.ReactNode; } type ViewRef = React.ElementRef<typeof View>; /** A preloaded source that loaded (and is now cached). */ export interface PreloadSuccess { /** The source's uri. */ uri: string; ok: true; /** The image's width (as in `onLoad`). */ width: number; /** The image's height (as in `onLoad`). */ height: number; } /** A preloaded source that failed to load. */ export interface PreloadFailure { /** The source's uri (none for a source without one, e.g. `null`). */ uri?: string; ok: false; /** What went wrong. */ error: string; } /** * `preload`'s result for a source. Check `ok` to tell which it is: e.g. * `if (result.ok)` narrows it to a `PreloadSuccess`, with its size. Reading * `width` or `error` without checking is a type error. */ export type PreloadResult = PreloadSuccess | PreloadFailure; export interface FastImageStaticProperties { resizeMode: typeof resizeMode; priority: typeof priority; cacheControl: typeof cacheControl; preload: (sources: Source[]) => Promise<PreloadResult[]>; /** * Removes every image from the memory cache. Resolves once it's done, or * not `ok` with the error if it couldn't (Android: Glide failed to * start). Never rejects. */ clearMemoryCache: () => Promise<ClearCacheResult>; /** * Removes every image from the disk cache, and the HTTP cache of * `cache: 'web'` images. Resolves once it's done, or not `ok` with the * error if it couldn't (Android: Glide failed to start). Never rejects. */ clearDiskCache: () => Promise<ClearCacheResult>; /** * The path of the source's downloaded file in the disk cache, downloading * it first if it isn't there (without decoding it). With * `cache: 'cacheOnly'`, or a `cacheKey` without a `uri`, it doesn't * download. Never rejects. */ getCachePath: (source: Source) => Promise<CachePathResult>; /** * Stores a local image file (a `file://` uri or a path, or on Android a * `content://` uri) as the source's image in the disk cache, so views and * preloads of the source show it without downloading it. A source with a * `cacheKey` doesn't need a `uri`. Resolves with its cached file. Doesn't * replace an image that's already cached, and isn't for `cache: 'web'` * sources. Never rejects. */ writeToCache: (source: Source, file: string) => Promise<CachePathResult>; /** * Changes the cache's limits at runtime (only those given; 0 for no * limit, null to go back to the app's native config) and saves them, then * resolves with the limits in effect and the disk cache's size; without * limits, it only resolves with them. iOS applies changes at once. * Android only has `maxDiskSize`, applied when Glide starts: at once if * it hasn't started yet in this launch, otherwise from the next launch. * FastImage starts it when its first view is created (before the view has * a source), and for `preload`, `getCachePath`, `writeToCache`, * `clearDiskCache` and `configureCache` itself, even without limits (e.g. * to read the disk cache's size). */ configureCache: (limits?: CacheLimits) => Promise<CacheState>; } declare const FastImage: React.ForwardRefExoticComponent<FastImageProps & React.RefAttributes<ViewRef>> & FastImageStaticProperties; export interface FastImageBackgroundProps extends Omit<FastImageProps, 'style' | 'children'> { /** The container's style; the image fills it. */ style?: StyleProp<ViewStyle>; /** The image's style. */ imageStyle?: StyleProp<ImageStyle>; /** A ref to the image (the FastImage inside). */ imageRef?: React.Ref<any>; /** Content shown on top of the image. */ children?: React.ReactNode; } /** * An image with content on top of it, like React Native's `ImageBackground`: a * view that the image fills, with the children on top. Use it rather than * giving `FastImage` children, which it won't render in the next major version * (the image will be a single native view). The other props go to the image; * the ref is the view's. */ export declare const FastImageBackground: React.ForwardRefExoticComponent<FastImageBackgroundProps & React.RefAttributes<ViewRef>>; export default FastImage;