Last active
March 3, 2023 12:56
-
-
Save mikroskeem/ad8a6fac78b045e169fc7a36596f4714 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script lang="ts"> | |
import LazyImage from "$lib/components/LazyImage.svelte"; | |
</script> | |
<style> | |
div.preview > :global(*) { | |
height: 320px; | |
width: 320px; | |
} | |
</style> | |
<div class="preview"> | |
<LazyImage src="https://picsum.photos/320/320" hash="LEHV6nWB2yk8pyo0adR*.7kCMdnj" /> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script lang="ts"> | |
import { decode, encode } from "blurhash"; | |
import { onMount } from "svelte"; | |
export let hash: string; | |
export let punch: number | undefined = undefined; | |
export let src: string; | |
export let alt: string | undefined = undefined; | |
let container: HTMLDivElement; | |
let canvas: HTMLCanvasElement; | |
let img: HTMLImageElement; | |
let width: number; | |
let height: number; | |
async function renderCanvas(canvas: HTMLCanvasElement, hash: string, punch?: number): Promise<void> { | |
// Ensure that canvas/image height & width match with displayed dimensions | |
canvas.width = width; | |
canvas.height = height; | |
const ctx = canvas.getContext("2d"); | |
if (!ctx) { | |
console.error("No 2d context", canvas); | |
return; | |
} | |
try { | |
const pixels = decode(hash, canvas.width, canvas.height, punch); | |
const imageData = ctx.createImageData(width, height); | |
imageData.data.set(pixels); | |
ctx.putImageData(imageData, 0, 0); | |
} catch (err) { | |
console.error("Failed to create placeholder image", err); | |
} | |
} | |
let loadingDone = false; | |
let loadStart: number; | |
function loadImage(hash: string, src: string): Promise<void> { | |
const renderStart = performance.now(); | |
const renderPromise = renderCanvas(canvas, hash, punch).then(() => { | |
const renderEnd = performance.now(); | |
console.debug("Image hash '%s' render done in %dms", hash, renderEnd - renderStart); | |
}); | |
loadStart = performance.now(); | |
img.crossOrigin = "anonymous"; | |
img.src = src; | |
return renderPromise; | |
} | |
async function imgToHash(image: HTMLImageElement): Promise<string> { | |
const canvas = document.createElement("canvas"); | |
const ctx = canvas.getContext("2d"); | |
if (!ctx) { | |
throw new Error("No context"); | |
} | |
canvas.width = image.width; | |
canvas.height = image.height; | |
ctx.drawImage(image, 0, 0); | |
const imageData = ctx.getImageData(0, 0, image.width, image.height); | |
return encode(imageData.data, imageData.width, imageData.height, 4, 4); | |
} | |
onMount(async () => { | |
// Grab initial size | |
width = container.clientWidth; | |
height = container.clientHeight; | |
img.addEventListener("load", () => { | |
const loadEnd = performance.now(); | |
console.debug("Image hash '%s' real image load done in %dms", hash, loadEnd - loadStart); | |
loadingDone = true | |
}); | |
loadImage(hash, src); | |
img.addEventListener("click", async () => { | |
if (!loadingDone) { | |
return; | |
} | |
loadingDone = false; | |
console.log("click, changing image"); | |
// Grab new hash | |
const hashGenStart = performance.now(); | |
hash = await imgToHash(img); | |
const hashGenEnd = performance.now(); | |
console.debug("Generating new hash '%s' took %dms", hash, hashGenEnd - hashGenStart); | |
setTimeout(() => { | |
const newURL = new URL(src); | |
newURL.searchParams.set("n", "" + new Date().getTime()); | |
loadImage(hash, newURL.toString()); | |
}, 10); | |
}); | |
}); | |
</script> | |
<style> | |
div { | |
display: grid; | |
grid-template-columns: 1fr; | |
grid-template-rows: 1fr; | |
} | |
div > * { | |
grid-row: 1; | |
grid-column: 1; | |
} | |
img { | |
transition: opacity 0.25s linear; | |
} | |
img.hidden { | |
opacity: 0; | |
} | |
</style> | |
<div bind:this={container}> | |
<canvas bind:this={canvas} /> | |
<img bind:this={img} class:hidden={!loadingDone} {alt} {width} {height} style="max-width: {width}px; max-height: {height}px;" /> | |
</div> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment