feat: add thumbhash loading to image
Some checks failed
Deploy to SFTP Server / build (push) Failing after 1m18s

This commit is contained in:
max_richter 2024-04-06 20:32:32 +02:00
parent 82eb0657e2
commit 5d59e2171d
7 changed files with 358 additions and 862 deletions

View File

@ -16,22 +16,15 @@
"@astrojs/tailwind": "^5.1.0", "@astrojs/tailwind": "^5.1.0",
"astro": "^4.5.5", "astro": "^4.5.5",
"astro-i18n-aut": "^0.7.0", "astro-i18n-aut": "^0.7.0",
"astro-imagetools": "^0.9.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.0",
"rehype-toc": "^3.0.2",
"remark-toc": "^9.0.0",
"svelte": "^4.2.12", "svelte": "^4.2.12",
"svelte-gestures": "^4.0.0", "svelte-gestures": "^4.0.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"thumbhash": "^0.1.1",
"typescript": "^5.4.2" "typescript": "^5.4.2"
}, },
"devDependencies": { "devDependencies": {
"@afordin/unocss-preset-token": "0.1.0-beta.0",
"@astrojs/sitemap": "^3.1.1", "@astrojs/sitemap": "^3.1.1",
"@iconify-json/tabler": "^1.1.109", "@iconify-json/tabler": "^1.1.109",
"@igor.dvlpr/astro-post-excerpt": "^2.1.0",
"@types/markdown-it": "^13.0.7", "@types/markdown-it": "^13.0.7",
"@unocss/preset-icons": "^0.58.8", "@unocss/preset-icons": "^0.58.8",
"@unocss/reset": "^0.58.8", "@unocss/reset": "^0.58.8",
@ -45,4 +38,4 @@
"unplugin-icons": "^0.18.5", "unplugin-icons": "^0.18.5",
"vite-plugin-glsl": "^1.3.0" "vite-plugin-glsl": "^1.3.0"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -48,6 +48,7 @@ const link = translatePath(`/${collection}/${slug.split("/")[0]}`);
cover && ( cover && (
<a href={link}> <a href={link}>
<Image <Image
hash
src={cover} src={cover}
alt={"cover for " + title} alt={"cover for " + title}
class="right-0 h-full object-cover object-center rounded-none border-l border-neutral" class="right-0 h-full object-cover object-center rounded-none border-l border-neutral"

View File

@ -6,10 +6,32 @@ interface Props {
alt: string; alt: string;
class?: string; class?: string;
caption?: string; caption?: string;
hash?: boolean;
maxWidth?: number; maxWidth?: number;
} }
import { rgbaToThumbHash } from "thumbhash";
import sharp from "sharp";
const { src: image, alt, maxWidth } = Astro.props; const { src: image, hash = true, alt, maxWidth } = Astro.props;
let thumbhash = "";
if (hash) {
const scaleFactor = 100 / Math.max(image.width, image.height);
const smallWidth = Math.floor(image.width * scaleFactor);
const smallHeight = Math.floor(image.height * scaleFactor);
const smallImg = await sharp(image.fsPath)
.resize(smallWidth, smallHeight)
.withMetadata()
.raw()
.ensureAlpha()
.toBuffer();
const buffer = rgbaToThumbHash(smallWidth, smallHeight, smallImg);
thumbhash = Buffer.from(buffer).toString("base64");
}
const sizes = [ const sizes = [
{ {
@ -33,6 +55,8 @@ const sizes = [
<AstroImage <AstroImage
src={image} src={image}
alt={alt} alt={alt}
data-thumbhash={thumbhash}
pictureAttributes={{ class: hash ? "block h-full" : "" }}
class={Astro.props.class} class={Astro.props.class}
widths={sizes.map((size) => size.width)} widths={sizes.map((size) => size.width)}
sizes={sizes sizes={sizes

View File

@ -13,7 +13,7 @@ const t = useTranslations(Astro.url);
classes="googley-eye-target relative rounded-diag-md border border-neutral gradient grid grid-cols-[250px_1fr] h-[180px] mt-8" classes="googley-eye-target relative rounded-diag-md border border-neutral gradient grid grid-cols-[250px_1fr] h-[180px] mt-8"
> >
<div class="image"> <div class="image">
<Image src={MaxImg} alt="its mee" maxWidth={700} /> <Image src={MaxImg} alt="its mee" hash={false} maxWidth={700} />
<div class="eye right"> <div class="eye right">
<GoogleyEye client:load /> <GoogleyEye client:load />
</div> </div>

View File

@ -0,0 +1,46 @@
<script>
import { thumbHashToRGBA, rgbaToDataURL } from "thumbhash";
function show(img: HTMLImageElement) {
img.style.opacity = "1";
img.style.filter = "blur(0px)";
setTimeout(() => {
img.parentNode.style.background = "";
}, 600);
}
document.querySelectorAll("[data-thumbhash]").forEach((entry) => {
const parent = entry?.parentNode as HTMLPictureElement;
const img = entry as HTMLImageElement;
if (parent?.nodeName !== "PICTURE") return;
const hash = img.getAttribute("data-thumbhash");
if (!hash) return;
const decodedString = atob(hash);
// Create Uint8Array from decoded string
const buffer = new Uint8Array(decodedString.length);
for (let i = 0; i < decodedString.length; i++) {
buffer[i] = decodedString.charCodeAt(i);
}
const image = thumbHashToRGBA(buffer);
const dataURL = rgbaToDataURL(image.w, image.h, image.rgba);
parent.style.background = `url(${dataURL})`;
parent.style.backgroundSize = "cover";
img.style.opacity = "0";
img.style.filter = "blur(5px)";
img.style.transition = "opacity 0.6s ease, filter 0.8s ease";
const sources = parent.querySelectorAll("source");
img.onload = () => show(img);
for (const source of sources) {
source.addEventListener("load", () => show(img));
}
});
</script>

View File

@ -8,6 +8,7 @@ interface Props {
} }
const { title, width = "compact" } = Astro.props; const { title, width = "compact" } = Astro.props;
import Thumbhash from "@components/Thumbhash.astro";
--- ---
<!doctype html> <!doctype html>
@ -126,6 +127,7 @@ const { title, width = "compact" } = Astro.props;
<footer> <footer>
<LanguagePicker /> <LanguagePicker />
</footer> </footer>
<Thumbhash />
<style> <style>
.layout-compact { .layout-compact {
max-width: 600px; max-width: 600px;