Skip to content

Commit bda8cf1

Browse files
committed
feat: auto-generate blurDataURL property when placeholder=blur
1 parent 87d85a1 commit bda8cf1

File tree

8 files changed

+227
-16
lines changed

8 files changed

+227
-16
lines changed

example/pages/index.tsx

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@ import { FC } from 'react';
55
import styles from '../styles/Home.module.css';
66

77
type CodeProps = {
8-
[key: string]: any
9-
}
10-
const Code: FC<CodeProps> = (p) =>
8+
[key: string]: any;
9+
};
10+
const Code: FC<CodeProps> = (p) => (
1111
<code className={styles.inlineCode} {...p} />
12+
);
1213

1314
const Home: NextPage = () => (
1415
<div className={styles.container}>
1516
<div className={styles.card}>
16-
<h1>Uploadcare custom loader for Image Component <a href="https://github.com/uploadcare/nextjs-loader">@uploadcare/nextjs-loader</a></h1>
17+
<h1>
18+
Uploadcare custom loader for Image Component{' '}
19+
<a href="https://github.com/uploadcare/nextjs-loader">
20+
@uploadcare/nextjs-loader
21+
</a>
22+
</h1>
1723
<p>
1824
The following is an example of a reference to an image from the{' '}
1925
Uploadcare CDN at <Code>ucarecdn.com</Code>
@@ -31,7 +37,8 @@ const Home: NextPage = () => (
3137
/>
3238
<hr className={styles.hr} />
3339
<p>
34-
The following is an example of use of the <Code>UploadcareImage</Code> helper component.
40+
The following is an example of use of the <Code>UploadcareImage</Code>{' '}
41+
helper component.
3542
</p>
3643
<UploadcareImage
3744
alt="Vercel logo"
@@ -40,6 +47,19 @@ const Home: NextPage = () => (
4047
height={500}
4148
/>
4249
<hr className={styles.hr} />
50+
<p>
51+
The following is an example of use of the <Code>UploadcareImage</Code>{' '}
52+
helper component with <Code>placeholder=blur</Code> property. It&apos;s
53+
better to enable network throttling in dev tools to see the blurred placeholder.
54+
</p>
55+
<UploadcareImage
56+
alt="Vercel logo"
57+
src="https://ucarecdn.com/c768f1c2-891a-4f54-8e1e-7242df218b51/pinewatt2Hzmz15wGikunsplash.jpg"
58+
width={500}
59+
height={500}
60+
placeholder="blur"
61+
/>
62+
<hr className={styles.hr} />
4363
<p>
4464
The following is an example of a reference to an external image at{' '}
4565
<Code>assets.vercel.com</Code>.
@@ -68,8 +88,11 @@ const Home: NextPage = () => (
6888
height={64}
6989
loader={uploadcareLoader}
7090
/>
71-
<hr className={styles.hr} />
72-
<p>Local image will be served AS IS in Development, and converted to the absolute URL and passed to the proxy in Production</p>
91+
<hr className={styles.hr} />
92+
<p>
93+
Local image will be served AS IS in Development, and converted to the
94+
absolute URL and passed to the proxy in Production
95+
</p>
7396
<Image
7497
alt="A local image"
7598
src="/local_image.png"
@@ -78,9 +101,13 @@ const Home: NextPage = () => (
78101
loader={uploadcareLoader}
79102
/>
80103
<hr className={styles.hr} />
81-
Checkout the project documentation on Github <a href="https://github.com/uploadcare/nextjs-loader">@uploadcare/nextjs-loader</a>.
104+
Checkout the project documentation on Github{' '}
105+
<a href="https://github.com/uploadcare/nextjs-loader">
106+
@uploadcare/nextjs-loader
107+
</a>
108+
.
82109
</div>
83110
</div>
84-
)
111+
);
85112

86-
export default Home
113+
export default Home;

jest-setup.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import '@testing-library/jest-dom'

jest.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export default {
22
testPathIgnorePatterns: ["<rootDir>/build/", "<rootDir>/src/__tests__/utils"],
3-
collectCoverageFrom: ["src/**/*.{ts,js,tsx}"]
3+
collectCoverageFrom: ["src/**/*.{ts,js,tsx}"],
4+
setupFilesAfterEnv: ['<rootDir>/jest-setup.js']
45
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@babel/preset-env": "^7.15.6",
4444
"@babel/preset-react": "^7.14.5",
4545
"@babel/preset-typescript": "^7.15.0",
46+
"@testing-library/jest-dom": "^5.16.4",
4647
"@testing-library/react": "^12.1.2",
4748
"@testing-library/react-hooks": "^7.0.2",
4849
"@types/jest": "^27.0.2",

src/__tests__/uploadcare-image.spec.tsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('UploadcareImage', () => {
1717
removeEnvVar('NEXT_PUBLIC_UPLOADCARE_PUBLIC_KEY');
1818
});
1919

20-
test('The UploadcareImage component renders passed image with default settings properly', () => {
20+
it('should render passed image with default settings properly', () => {
2121
const src =
2222
'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/vercel.png';
2323

@@ -38,7 +38,7 @@ describe('UploadcareImage', () => {
3838
);
3939
});
4040

41-
test('The UploadcareImage component should accept src without filename', () => {
41+
it('should accept src without filename', () => {
4242
const src = 'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/';
4343

4444
render(
@@ -56,4 +56,43 @@ describe('UploadcareImage', () => {
5656
'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/1080x/-/quality/normal/'
5757
);
5858
});
59+
60+
it('should generate blurDataURL when placeholder=blur passed', () => {
61+
const src =
62+
'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/image.png';
63+
64+
render(
65+
<UploadcareImage
66+
src={src}
67+
width={500}
68+
height={500}
69+
quality={80}
70+
placeholder="blur"
71+
/>
72+
);
73+
74+
expect(screen.getByRole('img')).toHaveStyle(
75+
'background-image: url(https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/-/format/auto/-/stretch/off/-/progressive/yes/-/resize/10x/-/quality/lightest/image.png)'
76+
);
77+
});
78+
79+
it('should not override passed blurDataURL', () => {
80+
const src =
81+
'https://ucarecdn.com/a6f8abc8-f92e-460a-b7a1-c5cd70a18cdb/image.png';
82+
83+
render(
84+
<UploadcareImage
85+
src={src}
86+
width={500}
87+
height={500}
88+
quality={80}
89+
placeholder="blur"
90+
blurDataURL={src}
91+
/>
92+
);
93+
94+
expect(screen.getByRole('img')).toHaveStyle(
95+
`background-image: url(${src})`
96+
);
97+
});
5998
});

src/components/UploadcareImage.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
import Image, { ImageProps } from 'next/image';
22
import React from 'react';
3+
import { getInt } from '../utils/helpers';
34
import { uploadcareLoader } from '../utils/loader';
45

5-
export function UploadcareImage(props: ImageProps): JSX.Element {
6-
return <Image loader={uploadcareLoader} {...props} />;
6+
export function UploadcareImage({
7+
blurDataURL,
8+
...props
9+
}: ImageProps): JSX.Element {
10+
if (
11+
props.placeholder === 'blur' &&
12+
!blurDataURL &&
13+
typeof props.src === 'string'
14+
) {
15+
const imageWidth = getInt(props.width);
16+
const blurImageWidth = imageWidth ? Math.ceil(imageWidth * 0.01) : 10;
17+
blurDataURL = uploadcareLoader({
18+
src: props.src,
19+
width: blurImageWidth,
20+
quality: 0
21+
});
22+
}
23+
return (
24+
<Image loader={uploadcareLoader} blurDataURL={blurDataURL} {...props} />
25+
);
726
}

src/utils/helpers.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ export function isJpegExtension(extension: string): boolean {
143143
return ['jpg', 'jpeg'].includes(extension.toLowerCase());
144144
}
145145

146+
export function getInt(x: unknown): number | undefined {
147+
if (typeof x === 'number') {
148+
return x;
149+
}
150+
if (typeof x === 'string') {
151+
return parseInt(x, 10);
152+
}
153+
return undefined;
154+
}
155+
146156
function _parseUploadcareTransformationParam(param: string): string[] {
147157
return param.split('/');
148158
}

0 commit comments

Comments
 (0)