Skip to content

Commit

Permalink
progressive img 로딩 최적화 (#1387)
Browse files Browse the repository at this point in the history
  • Loading branch information
devunt committed Nov 7, 2024
1 parent 457bb06 commit 466fc3b
Showing 1 changed file with 17 additions and 65 deletions.
82 changes: 17 additions & 65 deletions packages/ui/src/components/Img.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
<script lang="ts">
import { css } from '@readable/styled-system/css';
import { center } from '@readable/styled-system/patterns';
import qs from 'query-string';
import { base64 } from 'rfc4648';
import { onMount, tick } from 'svelte';
import { fade } from 'svelte/transition';
import { thumbHashToDataURL } from 'thumbhash';
import RingSpinner from './RingSpinner.svelte';
import type { SystemStyleObject } from '@readable/styled-system/types';
type Size = 16 | 24 | 32 | 48 | 64 | 96 | 128 | 256 | 512 | 1024 | 'full';
Expand All @@ -24,8 +21,7 @@
let { url, placeholder, ratio, alt, style, size, quality, progressive = false }: Props = $props();
let containerEl = $state<HTMLDivElement>();
let contentEl = $state<HTMLDivElement>();
let imgEl = $state<HTMLImageElement>();
let loaded = $state(false);
const src = $derived(qs.stringifyUrl({ url, query: { s: size === 'full' ? undefined : size, q: quality } }));
Expand All @@ -40,80 +36,36 @@
const placeholderUrl = $derived(
progressive && placeholder ? thumbHashToDataURL(base64.parse(placeholder)) : undefined,
);
const load = async () => {
const imgEl = new Image();
const onload = async () => {
if (loaded) {
return;
}
loaded = true;
await tick();
contentEl?.prepend(imgEl);
};
imgEl.addEventListener('load', onload);
imgEl.className = css(style);
if (sizes) imgEl.sizes = sizes;
if (srcset) imgEl.srcset = srcset;
imgEl.src = src;
imgEl.alt = alt;
await tick();
if (imgEl.complete) {
onload();
}
};
onMount(() => {
if (containerEl) {
const observer = new IntersectionObserver((entries) => {
if (entries.some((entry) => entry.isIntersecting)) {
observer.disconnect();
load();
}
});
observer.observe(containerEl);
return () => observer.disconnect();
}
});
</script>

<svelte:head>
{#if placeholderUrl}
<link as="image" href={src} imagesizes={sizes} imagesrcset={srcset} rel="preload" />
{/if}
</svelte:head>

{#if placeholderUrl}
<div
bind:this={containerEl}
style:aspect-ratio={ratio}
class={css({
position: 'relative',
width: 'full',
overflow: 'hidden',
})}
>
<img
style:aspect-ratio={ratio}
class={css({ objectFit: 'cover' }, style)}
bind:this={imgEl}
class={css(style)}
{alt}
loading="lazy"
src={placeholderUrl}
onload={() => (loaded = true)}
{sizes}
{src}
{srcset}
/>
{#if loaded}
<div bind:this={contentEl} class={css({ position: 'absolute', inset: '0' })} in:fade={{ duration: 200 }}></div>
{:else}
<div class={center({ position: 'absolute', inset: '0' })}>
<RingSpinner style={css.raw({ size: '20px' })} />
</div>

{#if !loaded}
<img
class={css({ position: 'absolute', inset: '0', size: 'full', objectFit: 'cover' })}
{alt}
loading="lazy"
src={placeholderUrl}
out:fade={{ duration: 200 }}
/>
{/if}
</div>
{:else}
Expand Down

0 comments on commit 466fc3b

Please sign in to comment.