Skip to content

Commit cb36b62

Browse files
committed
hero blur image responsive works, correct all responsive image sizes
1 parent 6cb503e commit cb36b62

File tree

20 files changed

+105
-107
lines changed

20 files changed

+105
-107
lines changed

docs/working-notes/todo4.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ eventi ne moze na <img /> ali moze na <div /> okolo, <Image /> ide u slot // no
128128
bolje props od getImage()
129129
-----
130130
must use <img /> tag for srcset and sizes to be in dom instead of const img = new Image();
131+
132+
loading... div bottom indicator
133+
set min-height blank gallery
131134
```
132135

133136

src/components/Gallery.astro

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,16 @@
11
---
2-
import ReactGallery from '@/components/react/Gallery';
32
import NewReactGallery from '@/components/react/NewGallery';
4-
import { getGalleryImages, getImagesProps } from '@/libs/gallery/images';
5-
import { sliceToMod4 } from '@/utils/array';
6-
import { allImagesMetadata, imageMetadataToReactImageProps } from '@/utils/image';
3+
import { getGalleryImages } from '@/libs/gallery/images';
74
import { randomizeArray } from '@/utils/objects';
8-
import { cn } from '@/utils/styles';
95
106
export interface Props extends astroHTML.JSX.HTMLAttributes {}
117
12-
const allImagesMetadataMod4 = sliceToMod4(allImagesMetadata);
13-
14-
const _reactImages = await Promise.all(
15-
allImagesMetadataMod4.map((metadata) => imageMetadataToReactImageProps(metadata))
16-
);
17-
18-
const reactImages = await getGalleryImages();
19-
20-
console.log('reactImages', JSON.stringify(reactImages[0], null, 2));
21-
22-
// const reactImages = await getImagesProps();
23-
24-
const randomizedReactImages = randomizeArray(reactImages);
25-
26-
// console.log('reactImages', reactImages);
8+
const galleryImages = await getGalleryImages();
9+
const randomizedGalleryImages = randomizeArray(galleryImages);
2710
2811
const { class: className } = Astro.props;
2912
---
3013

31-
<div class={cn('', className)}>
32-
<!-- <ReactGallery client:only="react" images={randomizedReactImages} /> -->
33-
<NewReactGallery client:only="react" images={randomizedReactImages} />
14+
<div class={className}>
15+
<NewReactGallery client:only="react" images={randomizedGalleryImages} />
3416
</div>

src/components/ImageRandom.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
---
22
import ImageRandomReact from '@/components/react/ImageRandom';
33
import { IMAGE_SIZES } from '@/constants/image';
4-
import { getImagesProps } from '@/libs/gallery/images';
4+
import { getHeroImages } from '@/libs/gallery/images';
55
import { cn } from '@/utils/styles';
66
77
const { class: className, ...props } = Astro.props;
88
9-
const galleryImages = await getImagesProps();
9+
const galleryImages = await getHeroImages();
1010
1111
// add 'px' suffix or styles will fail
1212
const { width, height } = Object.fromEntries(

src/components/PostCardMore.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const { title, heroImage, heroAlt, description, draft } = data;
1919

2020
<div>
2121
<Image
22-
{...IMAGE_SIZES.RESPONSIVE.POST_CARD}
22+
{...IMAGE_SIZES.RESPONSIVE.POST_CARD_MORE}
2323
src={heroImage}
2424
alt={heroAlt}
2525
class="max-h-40 object-cover rounded-t-box"

src/components/react/ImageBlurPreloader.tsx

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,67 +3,78 @@ import { useEffect, useState } from 'react';
33
import usePrevious from '@/components/react/hooks/usePrevious';
44
import { cn } from '@/utils/styles';
55

6-
import type { FC, ImgHTMLAttributes } from 'react';
6+
import type { ImgAttributes } from '@/libs/gallery/images';
7+
import type { FC } from 'react';
78

8-
interface Props extends ImgHTMLAttributes<HTMLImageElement> {
9-
blurSrc: string;
9+
interface Props {
10+
blurAttributes: ImgAttributes;
11+
mainAttributes: ImgAttributes;
1012
className?: string;
1113
divClassName?: string;
12-
onSrcLoaded?: () => void;
14+
onMainLoaded?: () => void;
1315
}
1416

15-
const initialSrc = '' as const;
17+
const initialAttributes: ImgAttributes = { src: '' } as const;
1618

1719
const ImageBlurPreloader: FC<Props> = ({
18-
blurSrc = initialSrc,
19-
src = initialSrc,
20-
onSrcLoaded,
20+
blurAttributes = initialAttributes,
21+
mainAttributes = initialAttributes,
22+
onMainLoaded,
2123
className,
2224
divClassName,
23-
width,
24-
height,
25-
...props
2625
}) => {
2726
const [hasLoaded, setHasLoaded] = useState(false);
28-
const prevSrc = usePrevious(src);
27+
const prevMainAttributes = usePrevious(mainAttributes);
2928

30-
// reset hasLoaded on src change
29+
// reset hasLoaded on main image change
3130
useEffect(() => {
32-
if (prevSrc !== src) {
31+
if (prevMainAttributes !== mainAttributes) {
3332
setHasLoaded(false);
3433
}
35-
}, [prevSrc, src, setHasLoaded]);
34+
}, [prevMainAttributes, mainAttributes, setHasLoaded]);
3635

37-
// handle blur -> xl image switch
38-
useEffect(() => {
39-
if (!src) return;
40-
41-
const img = new Image();
42-
img.src = src;
36+
// important: main image must be in DOM for onLoad to work
37+
// unmount and display: none will fail
38+
const handleLoad = () => {
39+
setHasLoaded(true);
40+
onMainLoaded?.();
41+
};
4342

44-
img.onload = () => {
45-
setHasLoaded(true);
46-
onSrcLoaded?.();
47-
};
48-
// src important dependency
49-
}, [src, setHasLoaded]);
43+
const commonAttributes = {
44+
// blur image must use size from main image
45+
width: mainAttributes.width,
46+
height: mainAttributes.height,
47+
};
5048

51-
const imageSrc = hasLoaded ? src : blurSrc;
49+
const hasImage = hasLoaded
50+
? mainAttributes.src || mainAttributes.srcSet
51+
: blurAttributes.src || blurAttributes.srcSet;
5252

5353
return (
54-
<>
55-
{imageSrc ? (
56-
<img
57-
{...props}
58-
src={imageSrc}
59-
width={width}
60-
height={height}
61-
className={cn('object-cover', className)}
62-
/>
63-
) : (
64-
<div className={divClassName} />
54+
<div className={cn('relative size-full', divClassName)}>
55+
{hasImage && (
56+
<>
57+
{/* blur image */}
58+
<img
59+
{...blurAttributes}
60+
{...commonAttributes}
61+
className={cn('object-cover absolute top-0 left-0 size-full', className)}
62+
/>
63+
64+
{/* main image */}
65+
<img
66+
{...mainAttributes}
67+
{...commonAttributes}
68+
onLoad={handleLoad}
69+
className={cn(
70+
'object-cover absolute top-0 left-0 size-full',
71+
hasLoaded ? 'opacity-100' : 'opacity-0',
72+
className
73+
)}
74+
/>
75+
</>
6576
)}
66-
</>
77+
</div>
6778
);
6879
};
6980

src/components/react/ImageRandom.tsx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,30 @@ import ImageBlurPreloader from '@/components/react/ImageBlurPreloader';
44
import { getRandomElementFromArray } from '@/utils/array';
55
import { cn } from '@/utils/styles';
66

7-
import type { ImageAttributes, ImageProps } from '@/libs/gallery/transform';
7+
import type { HeroImage, ImgAttributes } from '@/libs/gallery/images';
88
import type { FC, ImgHTMLAttributes } from 'react';
99

1010
interface Props extends ImgHTMLAttributes<HTMLImageElement> {
1111
// Important: must pass all allImagesSrc from the server or they wont be included on client
12-
galleryImages: ImageProps[];
12+
galleryImages: HeroImage[];
1313
divClassName?: string;
1414
}
1515

16-
const imageAttributes: ImageAttributes = {
16+
const imageAttributes: ImgAttributes = {
1717
src: '',
1818
width: 0,
1919
height: 0,
2020
};
2121

22-
const initialImage: ImageProps = {
22+
const initialImage: HeroImage = {
2323
blur: { ...imageAttributes },
24-
xs: { ...imageAttributes },
25-
md: { ...imageAttributes },
26-
xl: { ...imageAttributes },
24+
hero: { ...imageAttributes },
2725
};
2826

2927
const ImageRandomReact: FC<Props> = ({ galleryImages, className, divClassName, ...props }) => {
3028
const randomImage = useMemo(() => getRandomElementFromArray(galleryImages), [galleryImages]);
3129

3230
const [image, setImage] = useState(initialImage);
33-
const [hasLoaded, setHasLoaded] = useState(false);
3431

3532
// initial image, on mount
3633
useEffect(() => {
@@ -40,23 +37,25 @@ const ImageRandomReact: FC<Props> = ({ galleryImages, className, divClassName, .
4037
const handleClick = async () => {
4138
const randomImage = getRandomElementFromArray(galleryImages);
4239
setImage(randomImage);
43-
setHasLoaded(false);
4440
};
4541

46-
const imageAlt = hasLoaded ? 'Hero image' : 'Blur image';
42+
const moreBlurAttributes = {
43+
alt: 'Blur image',
44+
onClick: handleClick,
45+
};
46+
47+
const moreMainAttributes = {
48+
alt: 'Hero image',
49+
onClick: handleClick,
50+
};
4751

4852
return (
4953
<ImageBlurPreloader
5054
{...props}
51-
blurSrc={image.blur.src}
52-
src={image.xl.src}
53-
width={image.xl.width}
54-
height={image.xl.height}
55-
onSrcLoaded={() => setHasLoaded(true)}
56-
alt={imageAlt}
57-
onClick={handleClick}
58-
className={cn('cursor-pointer w-full h-full my-0', className)}
59-
divClassName={cn('w-full h-full', divClassName)}
55+
blurAttributes={{ ...image.blur, ...moreBlurAttributes }}
56+
mainAttributes={{ ...image.hero, ...moreMainAttributes }}
57+
className={cn('cursor-pointer my-0', className)}
58+
divClassName={divClassName}
6059
/>
6160
);
6261
};

src/components/react/NewGallery.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { useEffect, useMemo, useRef, useState } from 'react';
33
import debounce from 'lodash.debounce';
44
import PhotoSwipeLightbox from 'photoswipe/lightbox';
55

6-
import type { ImageProps } from '@/libs/gallery/transform';
76
import type { FC } from 'react';
87

98
import 'photoswipe/style.css';

src/constants/image.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,24 @@ export const IMAGE_SIZES = {
5858
},
5959
RESPONSIVE: {
6060
POST_CARD: {
61-
widths: [TW_SCREENS.XXS, TW_SCREENS.SM],
62-
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XXS}px, ${TW_SCREENS.SM}px`,
61+
widths: [TW_SCREENS.XXS, TW_SCREENS.XS, TW_SCREENS.SM, TW_SCREENS.MD],
62+
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XS}px, (max-width: ${TW_SCREENS.SM}px) ${TW_SCREENS.SM}px, (max-width: ${TW_SCREENS.MD}px) ${TW_SCREENS.MD}px, ${TW_SCREENS.XXS}px`,
63+
},
64+
POST_CARD_MORE: {
65+
widths: [TW_SCREENS.XXS, TW_SCREENS.XS, TW_SCREENS.SM],
66+
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XS}px, (max-width: ${TW_SCREENS.SM}px) ${TW_SCREENS.SM}px, ${TW_SCREENS.XXS}px`,
6367
},
6468
POST_HERO: {
65-
widths: [TW_SCREENS.XXS, TW_SCREENS.SM, TW_SCREENS.MD, TW_SCREENS.LG],
66-
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XXS}px, (max-width: ${TW_SCREENS.MD}px) ${TW_SCREENS.SM}px, (max-width: ${TW_SCREENS.XL}px) ${TW_SCREENS.MD}px, ${TW_SCREENS.LG}px`,
69+
widths: [TW_SCREENS.XS, TW_SCREENS.SM, TW_SCREENS.MD, TW_SCREENS.LG],
70+
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XS}px, (max-width: ${TW_SCREENS.SM}px) ${TW_SCREENS.SM}px, (max-width: ${TW_SCREENS.MD}px) ${TW_SCREENS.MD}px, ${TW_SCREENS.LG}px`,
6771
},
6872
PROJECT_CARD: {
69-
widths: [TW_SCREENS.XXS, TW_SCREENS.SM],
70-
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XXS}px, ${TW_SCREENS.SM}px`,
73+
widths: [TW_SCREENS.XXS, TW_SCREENS.XS, TW_SCREENS.SM],
74+
sizes: `(max-width: ${TW_SCREENS.SM}px) ${TW_SCREENS.SM}px, (max-width: ${TW_SCREENS.MD}px) ${TW_SCREENS.XS}px, ${TW_SCREENS.XXS}px`,
7175
},
7276
MDX_EXPAND_LG: {
73-
widths: [TW_SCREENS.XXS, TW_SCREENS.SM, TW_SCREENS.MD, TW_SCREENS.LG, TW_SCREENS.XL],
74-
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XXS}px, (max-width: ${TW_SCREENS.MD}px) ${TW_SCREENS.SM}px, (max-width: ${TW_SCREENS.XL}px) ${TW_SCREENS.LG}px, ${TW_SCREENS.XL}px`,
77+
widths: [TW_SCREENS.XS, TW_SCREENS.SM, TW_SCREENS.MD, TW_SCREENS.LG, TW_SCREENS.XL],
78+
sizes: `(max-width: ${TW_SCREENS.XS}px) ${TW_SCREENS.XS}px, (max-width: ${TW_SCREENS.MD}px) ${TW_SCREENS.MD}px, (max-width: ${TW_SCREENS.LG}px) ${TW_SCREENS.LG}px, ${TW_SCREENS.XL}px`,
7579
// for debugging
7680
// class: `border-8 border-blue-500 [@media(max-width:475px)]:!border-yellow-300 [@media(max-width:768px)]:border-orange-500 [@media(max-width:1280px)]:border-red-500`,
7781
},

src/content/project/2014/02-13-example-project-1/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ import FlowersImage from '../../../../content/project/2014/02-13-example-project
1515

1616
# This is the first project
1717

18-
<Image {...IMAGE_SIZES.FIXED.MDX_LG} src={FlowersImage} class="shadow-lg shadow-base-300" alt="" />
18+
<Image {...IMAGE_SIZES.RESPONSIVE.POST_HERO} src={FlowersImage} class="shadow-lg shadow-base-300" alt="" />

src/content/project/2014/03-15-example-project-2/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ import BicycleImage from '../../../../content/project/2014/03-15-example-project
1717

1818
# This is the second project
1919

20-
<Image {...IMAGE_SIZES.FIXED.MDX_LG} src={BicycleImage} class="shadow-lg shadow-base-300" alt="" />
20+
<Image {...IMAGE_SIZES.RESPONSIVE.POST_HERO} src={BicycleImage} class="shadow-lg shadow-base-300" alt="" />

0 commit comments

Comments
 (0)