diff --git a/example/src/pages/index.tsx b/example/src/pages/index.tsx
index a584adc..a38e293 100644
--- a/example/src/pages/index.tsx
+++ b/example/src/pages/index.tsx
@@ -44,19 +44,19 @@ export default function Home(): JSX.Element {
-
-
-
-
-
-
+
+
+
+
+
+
)
}
-function Card(props: { name: string; image: LoaderOutput }) {
+function Card(props: { name: string; image: LoaderOutput; swapOnLoad?: boolean }) {
return (
diff --git a/package-lock.json b/package-lock.json
index f5692aa..214f811 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"dependencies": {
"@docusaurus/core": "^3.5.1",
"@docusaurus/types": "^3.5.1",
+ "clsx": "^2.1.1",
"loader-utils": "^3.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
diff --git a/package.json b/package.json
index 6c1084d..14fe765 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"dependencies": {
"@docusaurus/core": "^3.5.1",
"@docusaurus/types": "^3.5.1",
+ "clsx": "^2.1.1",
"loader-utils": "^3.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
diff --git a/scripts/copy-css.js b/scripts/copy-css.js
index 290b9bb..8615b8a 100644
--- a/scripts/copy-css.js
+++ b/scripts/copy-css.js
@@ -1,3 +1,3 @@
import { copyFile } from 'fs/promises'
-await copyFile('src/theme/NativeIdealImage.module.css', 'lib/theme/NativeIdealImage.module.css')
+await copyFile('src/theme/NativeIdealImage.css', 'lib/theme/NativeIdealImage.css')
diff --git a/src/index.ts b/src/index.ts
index 05d999c..8270615 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -48,6 +48,11 @@ export type LoaderOutput = {
export type NativeIdealImageProps = ComponentProps<'img'> & {
readonly img: { default: string } | string | LoaderOutput
+ /**
+ * Swap (fade in) the actual image after it's fully loaded,
+ * requires JavaScript to work, so this might cause the image to load a bit slower
+ * */
+ swapOnLoad?: boolean
}
export default function pluginNativeIdealImage(
diff --git a/src/missing-types.d.ts b/src/missing-types.d.ts
index 423c636..a839446 100644
--- a/src/missing-types.d.ts
+++ b/src/missing-types.d.ts
@@ -1,4 +1 @@
-declare module '*.module.css' {
- const classes: { readonly [key: string]: string }
- export default classes
-}
+declare module '*.css' {}
diff --git a/src/theme/NativeIdealImage.css b/src/theme/NativeIdealImage.css
new file mode 100644
index 0000000..0d8d904
--- /dev/null
+++ b/src/theme/NativeIdealImage.css
@@ -0,0 +1,33 @@
+.native-ideal-img {
+ display: block;
+ position: relative;
+ overflow: hidden;
+}
+
+.native-ideal-img > img {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.native-ideal-img::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image: var(--lqip);
+ background-repeat: no-repeat;
+ background-size: cover;
+ filter: blur(10px);
+ transform: scale(1.1);
+ z-index: -1;
+ transition: opacity 300ms, transform 300ms;
+}
+
+.native-ideal-img.swap-on-load::after {
+ z-index: 1;
+}
+
+.native-ideal-img.swap-on-load.loaded::after {
+ opacity: 0;
+ transform: scale(1);
+}
diff --git a/src/theme/NativeIdealImage.module.css b/src/theme/NativeIdealImage.module.css
deleted file mode 100644
index ebcfdf7..0000000
--- a/src/theme/NativeIdealImage.module.css
+++ /dev/null
@@ -1,23 +0,0 @@
-.picture {
- display: block;
- position: relative;
- overflow: hidden;
-}
-
-.picture > img {
- display: block;
- width: 100%;
- height: auto;
-}
-
-.picture::after {
- content: '';
- position: absolute;
- inset: 0;
- background-image: var(--lqip);
- background-repeat: no-repeat;
- background-size: cover;
- filter: blur(10px);
- transform: scale(1.1);
- z-index: -1;
-}
diff --git a/src/theme/NativeIdealImage.tsx b/src/theme/NativeIdealImage.tsx
index 35f0ef6..39fda33 100644
--- a/src/theme/NativeIdealImage.tsx
+++ b/src/theme/NativeIdealImage.tsx
@@ -1,26 +1,35 @@
-import React from 'react'
+import React, { useEffect, useState } from 'react'
+import clsx from 'clsx'
import type { NativeIdealImageProps } from '../index.js'
-import styles from './NativeIdealImage.module.css'
+import './NativeIdealImage.css'
export default function NativeIdealImage(props: NativeIdealImageProps): JSX.Element {
- const { img, width, height, sizes, loading, ...propsRest } = props
+ const { img, width, height, sizes, loading, swapOnLoad, ...propsRest } = props
const formats = typeof img === 'object' && 'formats' in img ? img.formats : []
const lqip = typeof img === 'object' && 'lqip' in img ? img.lqip : ''
const singleImage = formats[0]?.srcSet.length === 1 ? formats[0] : undefined
+ const [placeHolderOnTop, setPlaceHolderOnTop] = useState(false)
+ const [loaded, setLoaded] = useState(false)
+
+ useEffect(() => {
+ if (!loaded) {
+ const id = setTimeout(() => setPlaceHolderOnTop(true), 50)
+ return () => clearTimeout(id)
+ }
+ }, [loaded])
+
return (