react-native-fast-image 10.2.0 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -5
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.flow +2 -0
- package/dist/index.cjs.web.js +20 -10
- package/dist/index.cjs.web.js.flow +2 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1 -1
- package/dist/index.js.flow +2 -0
- package/dist/index.web.js +21 -11
- package/dist/index.web.js.flow +2 -0
- package/package.json +1 -1
- package/src/index.js.flow +2 -0
- package/src/index.tsx +24 -0
- package/src/index.web.tsx +49 -24
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ npx expo install react-native-fast-image
|
|
|
71
71
|
|
|
72
72
|
It has native code, so it needs a [development build](https://docs.expo.dev/develop/development-builds/introduction/) (e.g. `npx expo run:ios`), not Expo Go.
|
|
73
73
|
|
|
74
|
-
Needs React Native 0.83 or later with the New Architecture (Expo SDK 55 or later), iOS/tvOS 15.1 or later, and Android 7.0 (API 24) or later; on tvOS, it works with [React Native tvOS](https://github.com/react-native-tvos/react-native-tvos). Apps on the legacy architecture or older React Native can use FastImage 9 (`npm install react-native-fast-image@9`). Upgrading from FastImage 9: see [Upgrading from 9.x](docs/upgrading.md).
|
|
74
|
+
Needs React Native 0.83 or later with the New Architecture (Expo SDK 55 or later) and React 19, iOS/tvOS 15.1 or later, and Android 7.0 (API 24) or later; on tvOS, it works with [React Native tvOS](https://github.com/react-native-tvos/react-native-tvos). Apps on the legacy architecture or older React Native can use FastImage 9 (`npm install react-native-fast-image@9`). Upgrading from FastImage 9: see [Upgrading from 9.x](docs/upgrading.md).
|
|
75
75
|
|
|
76
76
|
To set the cache's size when the app starts, see [Native config](#native-config).
|
|
77
77
|
|
|
@@ -161,6 +161,8 @@ A hint for which images to start loading first when several are waiting: `'high'
|
|
|
161
161
|
- `'normal'`: the default.
|
|
162
162
|
- `'high'`: e.g. the image the screen is about.
|
|
163
163
|
|
|
164
|
+
On the web, it's the image's `fetchpriority`.
|
|
165
|
+
|
|
164
166
|
---
|
|
165
167
|
|
|
166
168
|
##### `source.cache`
|
|
@@ -365,6 +367,22 @@ How the image is filtered when it's drawn smaller or larger than its size (like
|
|
|
365
367
|
|
|
366
368
|
---
|
|
367
369
|
|
|
370
|
+
#### `loading`
|
|
371
|
+
|
|
372
|
+
**Type:** `'lazy' | 'eager'` · **Default:** `'lazy'` · Web only
|
|
373
|
+
|
|
374
|
+
Whether the image loads as it nears the screen (`'lazy'`) or at once (`'eager'`).
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
#### `sizes`
|
|
379
|
+
|
|
380
|
+
**Type:** `string` · **Default:** `'auto, 100vw'` · Web only
|
|
381
|
+
|
|
382
|
+
With several sources, the width the image is shown at, as the image's [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#sizes), so the browser loads the size that fits. The default is the width the image is laid out at, which needs `loading="lazy"` (otherwise, or in browsers without it, the window's width).
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
368
386
|
#### `paused`
|
|
369
387
|
|
|
370
388
|
**Type:** `boolean`
|
|
@@ -932,14 +950,15 @@ The app needs access to the photo library, which it has if it got the url from t
|
|
|
932
950
|
|
|
933
951
|
## Web
|
|
934
952
|
|
|
935
|
-
FastImage works on the web with [react-native-web](https://necolas.github.io/react-native-web/). Bundlers pick the web version through the package's `browser` field, or the `.web.js` files next to the native ones.
|
|
953
|
+
FastImage works on the web with [react-native-web](https://necolas.github.io/react-native-web/) and React 19 or later. Bundlers pick the web version through the package's `browser` field, or the `.web.js` files next to the native ones.
|
|
936
954
|
|
|
937
955
|
Support is minimal:
|
|
938
956
|
|
|
939
|
-
- Props that work: `source` (a `uri`, a `require()`d image, or several sizes, with `thumbhash` and `blurDataUri`), `placeholder` (and `defaultSource`), `objectFit`, `objectPosition`, `tintColor`, `blurRadius`, `style`, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
940
|
-
- Ignored: `source.headers` (a browser can't send them for an image), `source.
|
|
957
|
+
- Props that work: `source` (a `uri`, a `require()`d image, or several sizes, with `thumbhash` and `blurDataUri`), `placeholder` (and `defaultSource`), `objectFit`, `objectPosition`, `tintColor`, `blurRadius`, `style`, `source.priority`, the web-only `loading` and `sizes`, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
958
|
+
- Ignored: `source.headers` (a browser can't send them for an image), `source.cache`, `source.cacheKey`, `source.memoryCache`, `source.diskCache`, `recyclingKey`, `loop`, `imageRendering`, `paused`, `transition`, `downsample` and `onProgress`.
|
|
941
959
|
- The image is an `<img>` element that fills the view inside its borders, with `objectFit` and `objectPosition` as its CSS `object-fit` and `object-position`. `tintColor` is an SVG filter and `blurRadius` a CSS `blur()`, and the placeholder is another `<img>`, under the image until it has loaded. A new `source` gets a new `<img>`, so the previous image doesn't stay while it loads (with several sizes, the browser shows it until the new one has loaded).
|
|
942
|
-
-
|
|
960
|
+
- Images load lazily unless [`loading`](#loading) is `'eager'`, and `source.priority` `'high'` and `'low'` are the `<img>`'s `fetchpriority`.
|
|
961
|
+
- Several sizes are shown with an `<img>` whose `srcset` lists them, with `sizes="auto, 100vw"` (or [`sizes`](#sizes)): the browser loads the one for the width the image is shown at, in device pixels (usually the smallest that's at least as wide). Browsers that don't support `sizes="auto"`, or an image with `loading="eager"`, use the viewport's width.
|
|
943
962
|
- `FastImage.preload` loads the images into the browser's cache and resolves with a result per source. `clearMemoryCache`, `clearDiskCache` and `configureCache` resolve without doing anything (the browser manages its cache), and `getCachePath` and `writeToCache` resolve with `{ ok: false, error: 'Not supported on the web' }`.
|
|
944
963
|
|
|
945
964
|
### ThumbHashes on the web
|
package/dist/index.cjs.js
CHANGED
|
@@ -347,7 +347,7 @@ function builtInTransition() {
|
|
|
347
347
|
return resolveTransition(react_native.Platform.OS === "android", NO_TRANSITION);
|
|
348
348
|
}
|
|
349
349
|
let defaultTransition;
|
|
350
|
-
function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, forwardedRef, ...viewProps }) {
|
|
350
|
+
function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
|
|
351
351
|
var _props$recyclingKey, _defaultTransition;
|
|
352
352
|
const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
|
|
353
353
|
if (__DEV__ && fallback !== void 0) warnOnce("fallback", "react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.", "warn");
|
package/dist/index.cjs.js.flow
CHANGED
|
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
149
149
|
recyclingKey?: ?string,
|
|
150
150
|
loop?: ?(boolean | number),
|
|
151
151
|
imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
|
|
152
|
+
loading?: ?('lazy' | 'eager'),
|
|
153
|
+
sizes?: ?string,
|
|
152
154
|
paused?: ?boolean,
|
|
153
155
|
transition?: ?(boolean | number | Transition),
|
|
154
156
|
downsample?: ?boolean,
|
package/dist/index.cjs.web.js
CHANGED
|
@@ -278,16 +278,12 @@ function dataUri(value) {
|
|
|
278
278
|
}
|
|
279
279
|
//#endregion
|
|
280
280
|
//#region src/index.web.tsx
|
|
281
|
-
var _useId;
|
|
282
281
|
const notSupported = {
|
|
283
282
|
ok: false,
|
|
284
283
|
error: "Not supported on the web"
|
|
285
284
|
};
|
|
286
|
-
let ids = 0;
|
|
287
|
-
const useCounterId = () => (0, react.useState)(() => `${++ids}`)[0];
|
|
288
|
-
const useSvgId = (_useId = react.default.useId) !== null && _useId !== void 0 ? _useId : useCounterId;
|
|
289
285
|
const useClientLayoutEffect = globalThis.document === void 0 ? react.useEffect : react.useLayoutEffect;
|
|
290
|
-
function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
286
|
+
function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
291
287
|
var _resolveUri;
|
|
292
288
|
if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
|
|
293
289
|
const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
|
|
@@ -297,12 +293,13 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
297
293
|
const src = sized ? sized.src : resolveUri(source);
|
|
298
294
|
const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
|
|
299
295
|
const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
296
|
+
const priority = sourcePriority(source);
|
|
300
297
|
const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
|
|
301
298
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
302
299
|
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
303
300
|
const [result, setResult] = (0, react.useState)();
|
|
304
301
|
const ok = result && result.key === key ? result.ok : void 0;
|
|
305
|
-
const svgId =
|
|
302
|
+
const svgId = (0, react.useId)().replace(/[^\w-]/g, "");
|
|
306
303
|
const tintId = `fast-image-tint-${svgId}`;
|
|
307
304
|
const blurId = `fast-image-blur-${svgId}`;
|
|
308
305
|
const fit = resolveObjectFit(objectFit, style);
|
|
@@ -322,6 +319,8 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
322
319
|
}, defaultUri && showDefault ? /* @__PURE__ */ react.default.createElement(Picture, {
|
|
323
320
|
key: `default ${defaultUri}`,
|
|
324
321
|
src: defaultUri,
|
|
322
|
+
loading,
|
|
323
|
+
priority,
|
|
325
324
|
fit: blurUri && (fit === "scale-down" || fit === "none") ? "contain" : fit,
|
|
326
325
|
position,
|
|
327
326
|
filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
|
|
@@ -330,7 +329,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
330
329
|
key: several ? "sizes" : key,
|
|
331
330
|
src,
|
|
332
331
|
srcSet,
|
|
333
|
-
|
|
332
|
+
sizes,
|
|
333
|
+
loading,
|
|
334
|
+
priority,
|
|
334
335
|
fit,
|
|
335
336
|
position,
|
|
336
337
|
filter,
|
|
@@ -375,6 +376,14 @@ function thumbhashUri(hash) {
|
|
|
375
376
|
if (decode) return decode(hash);
|
|
376
377
|
if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("thumbhash-web", "react-native-fast-image: on the web, a `thumbhash` shows only with `import 'react-native-fast-image/thumbhash'` (it decodes them). Or make it a `blurDataUri` before it reaches the browser (e.g. on your server) with that module's thumbHashToBlurDataUri, which needs no JavaScript in the browser.", "warn");
|
|
377
378
|
}
|
|
379
|
+
function sourcePriority(source) {
|
|
380
|
+
const values = (Array.isArray(source) ? source : [source]).map((entry) => entry && typeof entry === "object" ? entry.priority : void 0);
|
|
381
|
+
return [
|
|
382
|
+
"high",
|
|
383
|
+
"normal",
|
|
384
|
+
"low"
|
|
385
|
+
].find((priority) => values.includes(priority));
|
|
386
|
+
}
|
|
378
387
|
function shared(source, key, preview) {
|
|
379
388
|
const values = (Array.isArray(source) ? source : [source]).map((entry) => {
|
|
380
389
|
var _ref;
|
|
@@ -429,7 +438,7 @@ const fill = {
|
|
|
429
438
|
width: "100%",
|
|
430
439
|
height: "100%"
|
|
431
440
|
};
|
|
432
|
-
function Picture({ src, srcSet,
|
|
441
|
+
function Picture({ src, srcSet, sizes, loading, priority, fit, position, filter, hidden, alt, onLoad, onError }) {
|
|
433
442
|
const image = (0, react.useRef)(null);
|
|
434
443
|
const mounted = (0, react.useRef)(true);
|
|
435
444
|
const handled = (0, react.useRef)(void 0);
|
|
@@ -466,8 +475,9 @@ function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad
|
|
|
466
475
|
ref: image,
|
|
467
476
|
src,
|
|
468
477
|
srcSet,
|
|
469
|
-
sizes: srcSet ? "auto, 100vw" : void 0,
|
|
470
|
-
loading:
|
|
478
|
+
sizes: srcSet ? sizes !== null && sizes !== void 0 ? sizes : "auto, 100vw" : void 0,
|
|
479
|
+
loading: loading === "eager" ? void 0 : "lazy",
|
|
480
|
+
fetchPriority: priority === "high" || priority === "low" ? priority : void 0,
|
|
471
481
|
alt,
|
|
472
482
|
draggable: false,
|
|
473
483
|
style: {
|
|
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
149
149
|
recyclingKey?: ?string,
|
|
150
150
|
loop?: ?(boolean | number),
|
|
151
151
|
imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
|
|
152
|
+
loading?: ?('lazy' | 'eager'),
|
|
153
|
+
sizes?: ?string,
|
|
152
154
|
paused?: ?boolean,
|
|
153
155
|
transition?: ?(boolean | number | Transition),
|
|
154
156
|
downsample?: ?boolean,
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export type Source = {
|
|
|
35
35
|
* - `'normal'`: the default.
|
|
36
36
|
* - `'high'`: e.g. the image the screen is about.
|
|
37
37
|
*
|
|
38
|
+
* On the web, it's the image's `fetchpriority`.
|
|
39
|
+
*
|
|
38
40
|
* @default 'normal'
|
|
39
41
|
*/
|
|
40
42
|
priority?: Priority;
|
|
@@ -452,6 +454,25 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
452
454
|
* @default 'auto'
|
|
453
455
|
*/
|
|
454
456
|
imageRendering?: 'auto' | 'smooth' | 'pixelated';
|
|
457
|
+
/**
|
|
458
|
+
* Whether the image loads as it nears the screen (`'lazy'`) or at once
|
|
459
|
+
* (`'eager'`).
|
|
460
|
+
*
|
|
461
|
+
* @default 'lazy'
|
|
462
|
+
* @platform web
|
|
463
|
+
*/
|
|
464
|
+
loading?: 'lazy' | 'eager';
|
|
465
|
+
/**
|
|
466
|
+
* With several sources, the width the image is shown at, as the image's
|
|
467
|
+
* [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#sizes),
|
|
468
|
+
* so the browser loads the size that fits. The default is the width the
|
|
469
|
+
* image is laid out at, which needs `loading="lazy"` (otherwise, or in
|
|
470
|
+
* browsers without it, the window's width).
|
|
471
|
+
*
|
|
472
|
+
* @default 'auto, 100vw'
|
|
473
|
+
* @platform web
|
|
474
|
+
*/
|
|
475
|
+
sizes?: string;
|
|
455
476
|
/**
|
|
456
477
|
* Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
|
|
457
478
|
* the frame it's showing; `false` plays it again from there
|
package/dist/index.js
CHANGED
|
@@ -320,7 +320,7 @@ function builtInTransition() {
|
|
|
320
320
|
return resolveTransition(Platform.OS === "android", NO_TRANSITION);
|
|
321
321
|
}
|
|
322
322
|
let defaultTransition;
|
|
323
|
-
function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, forwardedRef, ...viewProps }) {
|
|
323
|
+
function FastImageBase({ source, placeholder, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loop, transition, loading: _loading, sizes: _sizes, forwardedRef, ...viewProps }) {
|
|
324
324
|
var _props$recyclingKey, _defaultTransition;
|
|
325
325
|
const { fallback, resizeMode: _resizeMode, ...props } = viewProps;
|
|
326
326
|
if (__DEV__ && fallback !== void 0) warnOnce("fallback", "react-native-fast-image: `fallback` was removed in 10.0, and does nothing: images always load natively. Remove it, or use React Native's Image for those images.", "warn");
|
package/dist/index.js.flow
CHANGED
|
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
149
149
|
recyclingKey?: ?string,
|
|
150
150
|
loop?: ?(boolean | number),
|
|
151
151
|
imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
|
|
152
|
+
loading?: ?('lazy' | 'eager'),
|
|
153
|
+
sizes?: ?string,
|
|
152
154
|
paused?: ?boolean,
|
|
153
155
|
transition?: ?(boolean | number | Transition),
|
|
154
156
|
downsample?: ?boolean,
|
package/dist/index.web.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { Children, forwardRef, memo, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { Children, forwardRef, memo, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { Image, PixelRatio, StyleSheet, View } from "react-native";
|
|
3
3
|
import { getAssetByID } from "react-native-web/dist/modules/AssetRegistry";
|
|
4
4
|
//#region src/constants.ts
|
|
@@ -251,16 +251,12 @@ function dataUri(value) {
|
|
|
251
251
|
}
|
|
252
252
|
//#endregion
|
|
253
253
|
//#region src/index.web.tsx
|
|
254
|
-
var _useId;
|
|
255
254
|
const notSupported = {
|
|
256
255
|
ok: false,
|
|
257
256
|
error: "Not supported on the web"
|
|
258
257
|
};
|
|
259
|
-
let ids = 0;
|
|
260
|
-
const useCounterId = () => useState(() => `${++ids}`)[0];
|
|
261
|
-
const useSvgId = (_useId = React.useId) !== null && _useId !== void 0 ? _useId : useCounterId;
|
|
262
258
|
const useClientLayoutEffect = globalThis.document === void 0 ? useEffect : useLayoutEffect;
|
|
263
|
-
function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
259
|
+
function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadius, onLoadStart, onLoad, onError, onLoadEnd, style, children, objectFit, objectPosition, loading, sizes, forwardedRef, onLayout, pointerEvents, recyclingKey: _recyclingKey, loop: _loop, imageRendering: _imageRendering, paused: _paused, transition: _transition, downsample: _downsample, onProgress: _onProgress, ...props }) {
|
|
264
260
|
var _resolveUri;
|
|
265
261
|
if (typeof __DEV__ !== "undefined" && __DEV__) warnIfChildren(children);
|
|
266
262
|
const { accessibilityLabel, "aria-label": ariaLabel, resizeMode: _resizeMode, ...viewProps } = props;
|
|
@@ -270,12 +266,13 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
270
266
|
const src = sized ? sized.src : resolveUri(source);
|
|
271
267
|
const srcSet = sized === null || sized === void 0 ? void 0 : sized.srcSet;
|
|
272
268
|
const key = srcSet !== null && srcSet !== void 0 ? srcSet : src;
|
|
269
|
+
const priority = sourcePriority(source);
|
|
273
270
|
const preview = hasSourcePlaceholder(placeholder) ? placeholder : void 0;
|
|
274
271
|
const blurUri = (_resolveUri = resolveUri(dataUri(shared(source, "blurDataUri", preview)))) !== null && _resolveUri !== void 0 ? _resolveUri : thumbhashUri(shared(source, "thumbhash", preview));
|
|
275
272
|
const defaultUri = blurUri !== null && blurUri !== void 0 ? blurUri : resolveUri(preview ? defaultSource : placeholder !== null && placeholder !== void 0 ? placeholder : defaultSource);
|
|
276
273
|
const [result, setResult] = useState();
|
|
277
274
|
const ok = result && result.key === key ? result.ok : void 0;
|
|
278
|
-
const svgId =
|
|
275
|
+
const svgId = useId().replace(/[^\w-]/g, "");
|
|
279
276
|
const tintId = `fast-image-tint-${svgId}`;
|
|
280
277
|
const blurId = `fast-image-blur-${svgId}`;
|
|
281
278
|
const fit = resolveObjectFit(objectFit, style);
|
|
@@ -295,6 +292,8 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
295
292
|
}, defaultUri && showDefault ? /* @__PURE__ */ React.createElement(Picture, {
|
|
296
293
|
key: `default ${defaultUri}`,
|
|
297
294
|
src: defaultUri,
|
|
295
|
+
loading,
|
|
296
|
+
priority,
|
|
298
297
|
fit: blurUri && (fit === "scale-down" || fit === "none") ? "contain" : fit,
|
|
299
298
|
position,
|
|
300
299
|
filter: blurUri ? [`url(#${blurId})`, tintColor ? `url(#${tintId})` : ""].filter(Boolean).join(" ") : filter,
|
|
@@ -303,7 +302,9 @@ function FastImageBase({ source, placeholder, defaultSource, tintColor, blurRadi
|
|
|
303
302
|
key: several ? "sizes" : key,
|
|
304
303
|
src,
|
|
305
304
|
srcSet,
|
|
306
|
-
|
|
305
|
+
sizes,
|
|
306
|
+
loading,
|
|
307
|
+
priority,
|
|
307
308
|
fit,
|
|
308
309
|
position,
|
|
309
310
|
filter,
|
|
@@ -348,6 +349,14 @@ function thumbhashUri(hash) {
|
|
|
348
349
|
if (decode) return decode(hash);
|
|
349
350
|
if (typeof __DEV__ !== "undefined" && __DEV__) warnOnce("thumbhash-web", "react-native-fast-image: on the web, a `thumbhash` shows only with `import 'react-native-fast-image/thumbhash'` (it decodes them). Or make it a `blurDataUri` before it reaches the browser (e.g. on your server) with that module's thumbHashToBlurDataUri, which needs no JavaScript in the browser.", "warn");
|
|
350
351
|
}
|
|
352
|
+
function sourcePriority(source) {
|
|
353
|
+
const values = (Array.isArray(source) ? source : [source]).map((entry) => entry && typeof entry === "object" ? entry.priority : void 0);
|
|
354
|
+
return [
|
|
355
|
+
"high",
|
|
356
|
+
"normal",
|
|
357
|
+
"low"
|
|
358
|
+
].find((priority) => values.includes(priority));
|
|
359
|
+
}
|
|
351
360
|
function shared(source, key, preview) {
|
|
352
361
|
const values = (Array.isArray(source) ? source : [source]).map((entry) => {
|
|
353
362
|
var _ref;
|
|
@@ -402,7 +411,7 @@ const fill = {
|
|
|
402
411
|
width: "100%",
|
|
403
412
|
height: "100%"
|
|
404
413
|
};
|
|
405
|
-
function Picture({ src, srcSet,
|
|
414
|
+
function Picture({ src, srcSet, sizes, loading, priority, fit, position, filter, hidden, alt, onLoad, onError }) {
|
|
406
415
|
const image = useRef(null);
|
|
407
416
|
const mounted = useRef(true);
|
|
408
417
|
const handled = useRef(void 0);
|
|
@@ -439,8 +448,9 @@ function Picture({ src, srcSet, lazy, fit, position, filter, hidden, alt, onLoad
|
|
|
439
448
|
ref: image,
|
|
440
449
|
src,
|
|
441
450
|
srcSet,
|
|
442
|
-
sizes: srcSet ? "auto, 100vw" : void 0,
|
|
443
|
-
loading:
|
|
451
|
+
sizes: srcSet ? sizes !== null && sizes !== void 0 ? sizes : "auto, 100vw" : void 0,
|
|
452
|
+
loading: loading === "eager" ? void 0 : "lazy",
|
|
453
|
+
fetchPriority: priority === "high" || priority === "low" ? priority : void 0,
|
|
444
454
|
alt,
|
|
445
455
|
draggable: false,
|
|
446
456
|
style: {
|
package/dist/index.web.js.flow
CHANGED
|
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
149
149
|
recyclingKey?: ?string,
|
|
150
150
|
loop?: ?(boolean | number),
|
|
151
151
|
imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
|
|
152
|
+
loading?: ?('lazy' | 'eager'),
|
|
153
|
+
sizes?: ?string,
|
|
152
154
|
paused?: ?boolean,
|
|
153
155
|
transition?: ?(boolean | number | Transition),
|
|
154
156
|
downsample?: ?boolean,
|
package/package.json
CHANGED
package/src/index.js.flow
CHANGED
|
@@ -149,6 +149,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
149
149
|
recyclingKey?: ?string,
|
|
150
150
|
loop?: ?(boolean | number),
|
|
151
151
|
imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
|
|
152
|
+
loading?: ?('lazy' | 'eager'),
|
|
153
|
+
sizes?: ?string,
|
|
152
154
|
paused?: ?boolean,
|
|
153
155
|
transition?: ?(boolean | number | Transition),
|
|
154
156
|
downsample?: ?boolean,
|
package/src/index.tsx
CHANGED
|
@@ -54,6 +54,8 @@ export type Source = {
|
|
|
54
54
|
* - `'normal'`: the default.
|
|
55
55
|
* - `'high'`: e.g. the image the screen is about.
|
|
56
56
|
*
|
|
57
|
+
* On the web, it's the image's `fetchpriority`.
|
|
58
|
+
*
|
|
57
59
|
* @default 'normal'
|
|
58
60
|
*/
|
|
59
61
|
priority?: Priority
|
|
@@ -474,6 +476,25 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
474
476
|
* @default 'auto'
|
|
475
477
|
*/
|
|
476
478
|
imageRendering?: 'auto' | 'smooth' | 'pixelated'
|
|
479
|
+
/**
|
|
480
|
+
* Whether the image loads as it nears the screen (`'lazy'`) or at once
|
|
481
|
+
* (`'eager'`).
|
|
482
|
+
*
|
|
483
|
+
* @default 'lazy'
|
|
484
|
+
* @platform web
|
|
485
|
+
*/
|
|
486
|
+
loading?: 'lazy' | 'eager'
|
|
487
|
+
/**
|
|
488
|
+
* With several sources, the width the image is shown at, as the image's
|
|
489
|
+
* [`sizes`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/img#sizes),
|
|
490
|
+
* so the browser loads the size that fits. The default is the width the
|
|
491
|
+
* image is laid out at, which needs `loading="lazy"` (otherwise, or in
|
|
492
|
+
* browsers without it, the window's width).
|
|
493
|
+
*
|
|
494
|
+
* @default 'auto, 100vw'
|
|
495
|
+
* @platform web
|
|
496
|
+
*/
|
|
497
|
+
sizes?: string
|
|
477
498
|
/**
|
|
478
499
|
* Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
|
|
479
500
|
* the frame it's showing; `false` plays it again from there
|
|
@@ -811,6 +832,9 @@ function FastImageBase({
|
|
|
811
832
|
objectPosition,
|
|
812
833
|
loop,
|
|
813
834
|
transition,
|
|
835
|
+
// Web only.
|
|
836
|
+
loading: _loading,
|
|
837
|
+
sizes: _sizes,
|
|
814
838
|
forwardedRef,
|
|
815
839
|
...viewProps
|
|
816
840
|
}: FastImageProps & { forwardedRef: React.Ref<any> }) {
|
package/src/index.web.tsx
CHANGED
|
@@ -11,15 +11,17 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Works: source (uri, require(), or several sizes: see sizedSources, and
|
|
13
13
|
// thumbhash and blurDataUri), placeholder (and defaultSource), objectFit,
|
|
14
|
-
// objectPosition, tintColor, blurRadius, style,
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// recyclingKey) and onProgress
|
|
14
|
+
// objectPosition, tintColor, blurRadius, style, source.priority (as
|
|
15
|
+
// fetchpriority), the web-only loading and sizes, onLoadStart, onLoad,
|
|
16
|
+
// onError, onLoadEnd, and View props (testID, accessibility, onLayout,
|
|
17
|
+
// pointerEvents). The native-only props (cache, headers, transition,
|
|
18
|
+
// downsample, loop, paused, imageRendering, recyclingKey) and onProgress
|
|
19
|
+
// are ignored.
|
|
19
20
|
import React, {
|
|
20
21
|
forwardRef,
|
|
21
22
|
memo,
|
|
22
23
|
useEffect,
|
|
24
|
+
useId,
|
|
23
25
|
useLayoutEffect,
|
|
24
26
|
useRef,
|
|
25
27
|
useState,
|
|
@@ -40,6 +42,7 @@ import type {
|
|
|
40
42
|
FastImageStaticProperties,
|
|
41
43
|
ObjectFit,
|
|
42
44
|
PreloadResult,
|
|
45
|
+
Priority,
|
|
43
46
|
Source,
|
|
44
47
|
SourcePlaceholder,
|
|
45
48
|
} from './index'
|
|
@@ -52,15 +55,6 @@ const notSupported: CachePathResult = {
|
|
|
52
55
|
error: 'Not supported on the web',
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
// An id that's the same on the server and in the browser: React's useId,
|
|
56
|
-
// from React 18 (this package's React types are older), without the
|
|
57
|
-
// characters an SVG id can't have (:r0: in React 18, «r0» in React 19). On
|
|
58
|
-
// React 17, a counter's (which can differ from the server's).
|
|
59
|
-
let ids = 0
|
|
60
|
-
const useCounterId = () => useState(() => `${++ids}`)[0]
|
|
61
|
-
const useSvgId: () => string =
|
|
62
|
-
(React as unknown as { useId?: () => string }).useId ?? useCounterId
|
|
63
|
-
|
|
64
58
|
// A layout effect in the browser (it runs before the image can show), and an
|
|
65
59
|
// effect when rendering on a server, where layout effects warn.
|
|
66
60
|
const useClientLayoutEffect =
|
|
@@ -86,6 +80,8 @@ function FastImageBase({
|
|
|
86
80
|
children,
|
|
87
81
|
objectFit,
|
|
88
82
|
objectPosition,
|
|
83
|
+
loading,
|
|
84
|
+
sizes,
|
|
89
85
|
forwardedRef,
|
|
90
86
|
// On the wrapper, the view the layout and touches are for here (on
|
|
91
87
|
// native, FastImage's only view).
|
|
@@ -125,6 +121,7 @@ function FastImageBase({
|
|
|
125
121
|
// one doesn't stay while it loads; with several sizes, the <img> stays,
|
|
126
122
|
// and the browser shows the previous image until the new one has loaded.
|
|
127
123
|
const key = srcSet ?? src
|
|
124
|
+
const priority = sourcePriority(source)
|
|
128
125
|
// The source's blurDataUri, or its ThumbHash made into one (with
|
|
129
126
|
// `react-native-fast-image/thumbhash`), shows blurred (see PreviewBlur)
|
|
130
127
|
// in place of `placeholder` (or `placeholder`'s, for a source without its
|
|
@@ -144,7 +141,7 @@ function FastImageBase({
|
|
|
144
141
|
const ok = result && result.key === key ? result.ok : undefined
|
|
145
142
|
// The same on the server and in the browser. React's ids have characters
|
|
146
143
|
// an SVG id can't (:r0: in React 18, «r0» in React 19).
|
|
147
|
-
const svgId =
|
|
144
|
+
const svgId = useId().replace(/[^\w-]/g, '')
|
|
148
145
|
const tintId = `fast-image-tint-${svgId}`
|
|
149
146
|
const blurId = `fast-image-blur-${svgId}`
|
|
150
147
|
// objectFit (the prop, then style's).
|
|
@@ -179,6 +176,9 @@ function FastImageBase({
|
|
|
179
176
|
<Picture
|
|
180
177
|
key={`default ${defaultUri}`}
|
|
181
178
|
src={defaultUri}
|
|
179
|
+
// Loaded when the image is (a data uri needs no request).
|
|
180
|
+
loading={loading}
|
|
181
|
+
priority={priority}
|
|
182
182
|
// A source's placeholder stands for an image whose size
|
|
183
183
|
// isn't known yet: as contain for scale-down and none,
|
|
184
184
|
// which show an image at its own size.
|
|
@@ -210,7 +210,9 @@ function FastImageBase({
|
|
|
210
210
|
key={several ? 'sizes' : key}
|
|
211
211
|
src={src}
|
|
212
212
|
srcSet={srcSet}
|
|
213
|
-
|
|
213
|
+
sizes={sizes}
|
|
214
|
+
loading={loading}
|
|
215
|
+
priority={priority}
|
|
214
216
|
fit={fit}
|
|
215
217
|
position={position}
|
|
216
218
|
filter={filter}
|
|
@@ -257,6 +259,20 @@ function thumbhashUri(hash: string | undefined): string | undefined {
|
|
|
257
259
|
return undefined
|
|
258
260
|
}
|
|
259
261
|
|
|
262
|
+
// A source's priority: one source's, or the highest of several sizes'.
|
|
263
|
+
function sourcePriority(
|
|
264
|
+
source: FastImageProps['source'],
|
|
265
|
+
): Priority | undefined {
|
|
266
|
+
const values = (Array.isArray(source) ? source : [source]).map((entry) =>
|
|
267
|
+
entry && typeof entry === 'object'
|
|
268
|
+
? (entry as Source).priority
|
|
269
|
+
: undefined,
|
|
270
|
+
)
|
|
271
|
+
return (['high', 'normal', 'low'] as const).find((priority) =>
|
|
272
|
+
values.includes(priority),
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
260
276
|
// The thumbhash or blurDataUri a source shows: its own, or `placeholder`'s
|
|
261
277
|
// (preview) if it has neither. With several sources, the one they all show.
|
|
262
278
|
function shared(
|
|
@@ -321,9 +337,9 @@ type PackagerAsset = {
|
|
|
321
337
|
// Several sizes of an image, as a srcset (each size's width, times its
|
|
322
338
|
// scale), from which the browser loads the one for the image's width in
|
|
323
339
|
// device pixels, usually the smallest at least as wide, as expo-image does.
|
|
324
|
-
// sizes="auto" has the browser use the width the image
|
|
325
|
-
// only applies to lazy images, and
|
|
326
|
-
// 100vw (the viewport's width). src is the largest, for browsers without
|
|
340
|
+
// sizes="auto" (the default sizes) has the browser use the width the image
|
|
341
|
+
// is laid out at; it only applies to lazy images, and otherwise, or in
|
|
342
|
+
// browsers without it, the next value, 100vw (the viewport's width). src is the largest, for browsers without
|
|
327
343
|
// srcset.
|
|
328
344
|
function sizedSources(sources: Source[]) {
|
|
329
345
|
const widths = sources
|
|
@@ -395,7 +411,9 @@ const fill = {
|
|
|
395
411
|
function Picture({
|
|
396
412
|
src,
|
|
397
413
|
srcSet,
|
|
398
|
-
|
|
414
|
+
sizes,
|
|
415
|
+
loading,
|
|
416
|
+
priority,
|
|
399
417
|
fit,
|
|
400
418
|
position,
|
|
401
419
|
filter,
|
|
@@ -406,8 +424,11 @@ function Picture({
|
|
|
406
424
|
}: {
|
|
407
425
|
src: string
|
|
408
426
|
srcSet?: string
|
|
409
|
-
//
|
|
410
|
-
|
|
427
|
+
// The props (see sizedSources for sizes).
|
|
428
|
+
sizes?: string
|
|
429
|
+
loading?: 'lazy' | 'eager'
|
|
430
|
+
// source.priority, as the <img>'s fetchpriority.
|
|
431
|
+
priority?: Priority
|
|
411
432
|
fit: ObjectFit
|
|
412
433
|
// objectPosition, as CSS.
|
|
413
434
|
position: string
|
|
@@ -478,8 +499,12 @@ function Picture({
|
|
|
478
499
|
ref={image}
|
|
479
500
|
src={src}
|
|
480
501
|
srcSet={srcSet}
|
|
481
|
-
sizes={srcSet ? 'auto, 100vw' : undefined}
|
|
482
|
-
loading={
|
|
502
|
+
sizes={srcSet ? (sizes ?? 'auto, 100vw') : undefined}
|
|
503
|
+
loading={loading === 'eager' ? undefined : 'lazy'}
|
|
504
|
+
// For 'high' and 'low' (the browser's own for 'normal').
|
|
505
|
+
fetchPriority={
|
|
506
|
+
priority === 'high' || priority === 'low' ? priority : undefined
|
|
507
|
+
}
|
|
483
508
|
alt={alt}
|
|
484
509
|
draggable={false}
|
|
485
510
|
style={{
|