Skip to content

Commit

Permalink
Add base64 dynamic blur data URL on image
Browse files Browse the repository at this point in the history
  • Loading branch information
irmantastam committed Jan 23, 2024
1 parent ff8002d commit b19f9d3
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 14 deletions.
1 change: 0 additions & 1 deletion public/assets/svg/blog-logo.svg

This file was deleted.

16 changes: 9 additions & 7 deletions src/components/features/contentful/CtfImage.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import NextImage, { ImageProps as NextImageProps } from 'next/image';
import { useState } from 'react';
import { twMerge } from 'tailwind-merge';

import { ImageFieldsFragment } from '@src/lib/__generated/sdk';

interface ImageProps extends Omit<ImageFieldsFragment, '__typename'> {
nextImageProps?: Omit<NextImageProps, 'src' | 'alt'>;
blurHash?: string;
}

export const CtfImage = ({ url, width, height, title, nextImageProps }: ImageProps) => {
if (!url || !width || !height) return null;
export const CtfImage = ({ url, width, height, title, nextImageProps, blurHash }: ImageProps) => {
const [isLoading, setIsLoading] = useState(true);

const blurURL = new URL(url);
blurURL.searchParams.set('w', '10');
if (!url || !width || !height) return null;

return (
<NextImage
src={url}
width={width}
height={height}
alt={title || ''}
sizes="(max-width: 1200px) 100vw, 50vw"
placeholder="blur"
blurDataURL={blurURL.toString()}
blurDataURL={blurHash}
{...nextImageProps}
className={twMerge(nextImageProps?.className, 'transition-all')}
className={twMerge(nextImageProps?.className, `${isLoading ? 'blur-md ' : ''}transition-all`)}
onLoad={() => setIsLoading(false)}
priority
/>
);
};
35 changes: 29 additions & 6 deletions src/pages/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import Instagram from '@icons/instagram.svg';
import Linkedin from '@icons/linkedin.svg';
import Quora from '@icons/quora.svg';
import Resume from '@icons/resume.svg';
import Stackshare from '@icons/stackshare.svg';
import { ArticleHero, ArticleImage, ArticleContent } from '@src/components/features/article';
import { SeoFields } from '@src/components/features/seo';
import { Container } from '@src/components/shared/container';
import { client, previewClient } from '@src/lib/client';
import { revalidateDuration } from '@src/pages/utils/constants';
import { dynamicBlurDataUrl } from '@src/utilities/dynamicBlurDataUrl';

const Page = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
const { t } = useTranslation();
Expand All @@ -35,19 +37,19 @@ const Page = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
{page.seoFields && <SeoFields {...page.seoFields} />}

{page.featuredBlogPost && (
<Container className="mb-8 p-0">
<Container className="mb-8">
<Link href={`/blog/${page.featuredBlogPost.slug}`}>
<ArticleHero article={page.featuredBlogPost} />
</Link>
</Container>
)}

<Container className="mx-auto max-w-xl p-0 text-center">
<Container className="text-center">
{page.image && (
<Container className="mb-8 max-w-xs p-0">
<ArticleImage
image={page.image}
className="aspect-square rounded-full border-none object-cover"
className="aspect-square rounded-full object-cover object-[55%] contrast-[110%] sepia-[25%]"
/>
</Container>
)}
Expand All @@ -66,7 +68,7 @@ const Page = (props: InferGetStaticPropsType<typeof getStaticProps>) => {

<Container className="p-0">
<h2 className="mb-2">{t('landingPage.reachMe')}</h2>
<ul className="mb-10 flex flex-wrap justify-center gap-2">
<ul className="mb-10 flex flex-wrap justify-center gap-1">
<li>
<a
href="https://www.linkedin.com/in/irmantas-tama%C5%A1auskas-6589272a6"
Expand Down Expand Up @@ -120,10 +122,20 @@ const Page = (props: InferGetStaticPropsType<typeof getStaticProps>) => {
<Quora className="transition-transform hover:-translate-y-0.5" />
</a>
</li>
<li>
<a
href="https://stackshare.io/irmantastam/my-stack"
target="_blank"
rel="noopener noreferrer"
title="Stackshare"
>
<Stackshare className="transition-transform hover:-translate-y-0.5" />
</a>
</li>
</ul>
</Container>

<Container className="mb-4 p-0">
<Container className="mb-4">
<a
href="/resume"
target="_blank"
Expand Down Expand Up @@ -158,7 +170,18 @@ export const getStaticProps: GetStaticProps = async ({ locale, draftMode: previe
props: {
previewActive: !!preview,
...(await getServerSideTranslations(locale)),
page,
page: {
...page,
image: {
...page.image,
image: {
...page.image?.image,
blurHash: page.image?.image?.url
? await dynamicBlurDataUrl(page.image.image.url)
: '',
},
},
},
},
};
} catch {
Expand Down
26 changes: 26 additions & 0 deletions src/utilities/dynamicBlurDataUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const baseUrl =
process.env.NODE_ENV === 'development'
? 'http://localhost:3000/'
: process.env.NEXT_PUBLIC_DOMAIN;

export async function dynamicBlurDataUrl(url: string) {
const base64str = await fetch(`${baseUrl}/_next/image?url=${url}&w=16&q=75`).then(async res =>
Buffer.from(await res.arrayBuffer()).toString('base64'),
);

const blurSvg = `
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 5'>
<filter id='b' color-interpolation-filters='sRGB'>
<feGaussianBlur stdDeviation='1' />
</filter>
<image preserveAspectRatio='none' filter='url(#b)' x='0' y='0' height='100%' width='100%'
href='data:image/avif;base64,${base64str}' />
</svg>
`;

const toBase64 = (str: string) =>
typeof window === 'undefined' ? Buffer.from(str).toString('base64') : window.btoa(str);

return `data:image/svg+xml;base64,${toBase64(blurSvg)}`;
}

0 comments on commit b19f9d3

Please sign in to comment.