My personal photography website steve.phot
portfolio photography svelte sveltekit

feat: initial thumb blur

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