In this blog post, we'll learn how to create Apple Music's background blur effect using Blurhash. I often wonder how Apple adds this blur effect in their app while playing the music. It's super cool if you open the playing music on the Apple Music app. The background blur color changes based on the playing music thumbnail image. This feature intrigued me to find the approach to how to do it. In this blog post, we'll learn how to create this effect.
I assume that everyone knows how to create a Next.js project. If you need help, follow this link to get started. Now we need to install the following packages into our project.
# yarn
yarn add blurhash react-blurhash sharp
# npm
npm install --save blurhash react-blurhash sharp
Let's start coding. First, we will create an API that takes a URL as a parameter and returns the blurhash string. I will use the Next.js v13 App router to implement this feature. If you are unfamiliar with this App folder approach, use the earlier Page router approach to achieve this behavior.
// app/api/gethash/route.ts
import * as blurhash from 'blurhash'
import { NextResponse } from 'next/server' import sharp from 'sharp';
export async function GET(req: Request) {
const { searchParams } = new URL(req.url) const url = searchParams.get('url')
if (!url) return NextResponse.json({
message: 'Required fields is empty'
}, {
status: 400
})
try {
const image = await fetch(url);
const buffer = Buffer.from(await image.arrayBuffer())
const { data, info } = await sharp(buffer)
.raw()
.ensureAlpha()
.resize(32, 32)
.toBuffer({
resolveWithObject: true
});
const hash = blurhash.encode(
new Uint8ClampedArray(data),
info.width,
info.height,
4,
4
)
return NextResponse.json({
hash
})
} catch (err) {
return NextResponse.json({
error: err
}, {
status: 404
})
}
}
We created a new route handler file inside the app/api/gethash
folder. The route handler file name should be route.js|ts
. While working with route handler files, you must be very conscious of the page.js file, and the route.js should be at a different level. Check this link for further details.
Let's break the code line by line to understand what is happening.
As mentioned above, our route takes the URL as a parameter. We need to get the the URL passed in the query params and check whether the value of the URL is empty or not.
const { searchParams } = new URL(req.url) const url = searchParams.get('url')
if (!url) return NextResponse.json({
message: 'Required fields is empty'
}, {
status: 400
})
const image = await fetch(url); const buffer = Buffer.from(await image.arrayBuffer())
const { data, info } = await sharp(buffer) .raw() .ensureAlpha() .resize(32, 32) .toBuffer({ resolveWithObject: true });
const hash = blurhash.encode( new Uint8ClampedArray(data), info.width, info.height, 4, 4 )
// page.tsx
import Image from 'next/image'
import BlurHash from './components/BlurHash'
async function getBlurHash() {
const response = await fetch('http://localhost:3000/api/gethash?url=https://plus.unsplash.com/premium_photo-1685077715983-772598c45360?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=200')
return await response.json()
}
export default async function Home() {
const { hash } = await getBlurHash()
return (
<main className='main'>
<div className='hash'>
<BlurHash hash={hash} />
</div>
<Image src='https://plus.unsplash.com/premium_photo-1685077715983-772598c45360?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=986&q=80' alt='' width={500} height={500} />
</main>
)
}
// components/BlurHash.tsx
'use client';
import React from 'react'
import { Blurhash } from 'react-blurhash';
const BlurHash = ({ hash }: { hash: string }) => {
return (
<Blurhash
hash={hash}
width={"100%"}
height={"100%"}
resolutionX={32}
resolutionY={32}
punch={1}
/>
)
}
export default BlurHash
Here is the link to my repo. If you have any questions or comments, let me know on Twitter at @jana__sundar or via email at mailtojana23[at]gmail.com. But until next time, happy coding!
Also published here.