Skip to content

Instantly share code, notes, and snippets.

@mikroskeem
Last active March 3, 2023 12:56
Show Gist options
  • Save mikroskeem/ad8a6fac78b045e169fc7a36596f4714 to your computer and use it in GitHub Desktop.
Save mikroskeem/ad8a6fac78b045e169fc7a36596f4714 to your computer and use it in GitHub Desktop.
<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>
<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