tangled
alpha
login
or
join now
stevedylan.dev
/
steve.photo
2
fork
atom
My personal photography website
steve.phot
portfolio
photography
svelte
sveltekit
2
fork
atom
overview
issues
pulls
pipelines
feat: initial thumb blur
stevedylan.dev
1 month ago
f88f0bc9
fc8c359e
+102
-21
3 changed files
expand all
collapse all
unified
split
src
lib
components
ProgressiveImage.svelte
routes
+page.svelte
photo
[slug]
+page.svelte
+70
src/lib/components/ProgressiveImage.svelte
···
1
1
+
<script lang="ts">
2
2
+
let {
3
3
+
src,
4
4
+
thumb,
5
5
+
alt,
6
6
+
class: className = "",
7
7
+
}: {
8
8
+
src: string;
9
9
+
thumb: string;
10
10
+
alt: string;
11
11
+
class?: string;
12
12
+
} = $props();
13
13
+
14
14
+
let loaded = $state(false);
15
15
+
let thumbAspect = $state(0);
16
16
+
let thumbImg: HTMLImageElement;
17
17
+
18
18
+
function onThumbLoad() {
19
19
+
if (thumbImg.naturalWidth && thumbImg.naturalHeight) {
20
20
+
thumbAspect = thumbImg.naturalWidth / thumbImg.naturalHeight;
21
21
+
}
22
22
+
}
23
23
+
24
24
+
$effect(() => {
25
25
+
loaded = false;
26
26
+
const img = new Image();
27
27
+
img.onload = () => {
28
28
+
loaded = true;
29
29
+
};
30
30
+
img.src = src;
31
31
+
32
32
+
return () => {
33
33
+
img.onload = null;
34
34
+
};
35
35
+
});
36
36
+
</script>
37
37
+
38
38
+
<div
39
39
+
class="progressive-container"
40
40
+
style="max-width: 4000px; {thumbAspect ? `aspect-ratio: ${thumbAspect};` : ''}"
41
41
+
>
42
42
+
<img
43
43
+
bind:this={thumbImg}
44
44
+
src={loaded ? src : thumb}
45
45
+
{alt}
46
46
+
class="{className} progressive-image"
47
47
+
class:progressive-loading={!loaded}
48
48
+
onload={onThumbLoad}
49
49
+
/>
50
50
+
</div>
51
51
+
52
52
+
<style>
53
53
+
.progressive-container {
54
54
+
width: 100%;
55
55
+
}
56
56
+
57
57
+
.progressive-container .progressive-image {
58
58
+
width: 100%;
59
59
+
height: 100%;
60
60
+
object-fit: contain;
61
61
+
}
62
62
+
63
63
+
.progressive-image {
64
64
+
transition: filter 0.4s ease-out;
65
65
+
}
66
66
+
67
67
+
.progressive-loading {
68
68
+
filter: blur(20px);
69
69
+
}
70
70
+
</style>
+25
-20
src/routes/+page.svelte
···
1
1
<script lang="ts">
2
2
-
import type { PageData } from "./$types";
3
3
-
type ImageItem = PageData["photos"][number];
4
4
-
let { data }: { data: PageData } = $props();
2
2
+
import type { PageData } from "./$types";
3
3
+
import ProgressiveImage from "$lib/components/ProgressiveImage.svelte";
4
4
+
type ImageItem = PageData["photos"][number];
5
5
+
let { data }: { data: PageData } = $props();
5
6
</script>
6
7
7
8
<div class="bg-[#121113] min-h-screen text-white">
···
10
11
</div>
11
12
12
13
{#snippet figure(image: ImageItem)}
13
13
-
<div class="flex sm:flex-row flex-col gap-2 sm:px-8 px-4 pt-2">
14
14
-
<a href="/photo/{image.slug}" class="flex-2 min-w-0">
15
15
-
<img class="max-w-full h-auto block" src={image.image} alt={image.title} />
16
16
-
</a>
17
17
-
<div class="flex flex-col gap-1 flex-1 min-w-0 p-4">
18
18
-
<h2 class="text-lg">{image.title.toUpperCase()}</h2>
19
19
-
<h3 class="text-sm">{image.make} {image.camera}</h3>
20
20
-
<div class="flex flex-col gap-2 text-neutral-400 font-thin text-xs mt-4">
21
21
-
<p>{image.focalLength}</p>
22
22
-
<p>{image.aperture}</p>
23
23
-
<p>{image.exposure}</p>
24
24
-
<p>ISO {image.iso}</p>
25
25
-
<p>-</p>
26
26
-
<p class="text-neutral-700 text-xs">{new Date(image.date).toLocaleDateString()}</p>
14
14
+
<div class="flex sm:flex-row flex-col gap-2 sm:px-8 px-4 pt-2">
15
15
+
<a href="/photo/{image.slug}" class="flex-2 min-w-0">
16
16
+
<ProgressiveImage
17
17
+
class="max-w-full h-auto block"
18
18
+
src={image.image}
19
19
+
thumb={image.thumb}
20
20
+
alt={image.title}
21
21
+
/>
22
22
+
</a>
23
23
+
<div class="flex flex-col gap-1 flex-1 min-w-0 p-4">
24
24
+
<h2 class="text-lg">{image.title.toUpperCase()}</h2>
25
25
+
<h3 class="text-sm">{image.make} {image.camera}</h3>
26
26
+
<div class="flex flex-col gap-2 text-neutral-400 font-thin text-xs mt-4">
27
27
+
<p>{image.focalLength}</p>
28
28
+
<p>{image.aperture}</p>
29
29
+
<p>{image.exposure}</p>
30
30
+
<p>ISO {image.iso}</p>
31
31
+
<p>-</p>
32
32
+
<p class="text-neutral-700 text-xs">{new Date(image.date).toLocaleDateString()}</p>
33
33
+
</div>
27
34
</div>
28
28
-
29
35
</div>
30
30
-
</div>
31
36
{/snippet}
32
37
33
38
<div class="flex flex-col gap-2 pt-12">
34
39
{#each data.photos as image}
35
35
-
{@render figure(image)}
40
40
+
{@render figure(image)}
36
41
{/each}
37
42
</div>
38
43
</div>
+7
-1
src/routes/photo/[slug]/+page.svelte
···
1
1
<script lang="ts">
2
2
import type { PageData } from "./$types";
3
3
+
import ProgressiveImage from "$lib/components/ProgressiveImage.svelte";
3
4
let { data }: { data: PageData } = $props();
4
5
</script>
5
6
···
10
11
11
12
<div class="flex sm:flex-row flex-col gap-2 sm:px-8 px-4 pt-16">
12
13
<div class="flex-6 min-w-0">
13
13
-
<img class="max-w-full h-auto block" src={data.photo.image} alt={data.photo.title} />
14
14
+
<ProgressiveImage
15
15
+
class="max-w-full h-auto block"
16
16
+
src={data.photo.image}
17
17
+
thumb={data.photo.thumb}
18
18
+
alt={data.photo.title}
19
19
+
/>
14
20
</div>
15
21
<div class="flex flex-col p-4 flex-1 min-w-0 justify-between">
16
22
<div class="flex flex-col gap-1 flex-1 min-w-0">