How to apply the blur effect to the background image
2023/03/02In this tutorial, we will add blur effect to a div with the background image step by step
Front-end
Vue3
Blurhash
Cover image thanks toTyler Casey


Before we start...

When building this website, I found that the page of articles always leaves a lot of blank space while loading those image covers. It's very boring and crude to leave those spaces, so I was thinking about adding some interesting effects while waiting for the loading.

I came up with the blur effect immediately, cause I have seen a similar effect in Nextjs <Image>. So, I decided to apply the same effect to my article's covers. After hours of searching and coding, I finally achieved this effect.

Briefly, there will be two layers:

  1. The blur image layer, which could be loaded immediately
  2. The image layer, which might take some time to download

The blur layer's opacity is 100 while downloading the image, As long as the image is completely downloaded, the blur layer's opacity will transit to 0 and reveal the image. You could hover over the picture below👇, it shows what I mean

Now, let's build this <ImageBg> with blur effect step by step!

✨Pre-requisite
  • Know the base knowledge of HTML, CSS, JS, and Vue3
  • Have a playground where you can build the Vue component (like Codepen)
  • Better have a look at this Blurhash, where we would generate the 'magic code' of blur images

1. A div with the image background

At the first, create a new component called ImageBg.vue. It will contain a blur layer and an image layer.

Then let's build a div with a simple image background in ImageBg.vue.

ImageBg.vue
vue
<template>
  <div class="container">
  </div>
</template>

<style scoped>
.container {
  position: relative;
  width: 300px;
  height: 200px;
  background-image: url(/img/article_cover/blur_cover.jpg);
}
</style>

Now, you will have a div, whose width is 300px and height is 200px, with an image background.

But there is a little bug, the image is not fully displayed. It keeps the original size and only shows its left top corner. Let's fix this by adding some background image settings.

2. Center and resize the background image

ImageBg.vue
vue
<template>
  ...
</template>

<style scoped>
.container {
  ...
  background-image: url(/img/article_cover/blur_cover.jpg);
  background-position: center;
  background-size: cover;
}
</style>

Add some settings in CSS, and we could see the image is displayed properly.

✨TIP
  • background-position: means the relative position between the background image and div. We pass 'center' to it means that the image would be placed at the center of the div.
  • background-size: 'cover' means the image will try to fill the whole div and keep its aspect ratio. The image won't be deformed.

Now, the image layer is fully prepared. In the next step, we will learn how to generate a blurred image and make it a component.

3. Blur an image with Blurhash

Blurhash provides an algorithm to convert an image to a Blurhash string, which presents a blurred image and can be parsed by <canvas>.

The process of an image transfer to a string is called 'encode', and the process of a string converting to an image is called 'decode'. Now, please upload one image you like and keep the Blurhash string. For me, it is:

  • LIA_9M%3L~-.yYRO8{OGuPX8ITo#

Woltapp, the creator of the Blurhash, has provided the SDK in many languages, with those tools, you could automate the 'encode' process easily. But in this tutorial, we would just simply drag the image to its website, and get the Blurhash string.

To decode this string we need to use Blurhash's SDK in JS language. Let's install it.

bash
# use npm
npm install blurhash

# or use yarn
yarn add blurhash

4. Build the skeleton of <ImageBlur>

Firstly, we will create a new component called <ImageBlur> only has the skeleton.

ImageBlur.vue
vue
<template>
  <div class="wrapper">
    <canvas class="canvas" width=32 height=32 />
  </div>
</template>

<script setup lang='ts'>
</script>

<style scoped>
.wrapper {
  position: relative;
  height: 0;
}

.canvas {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  width: 100%;
  height: 100%;
  z-index: 10;
}
</style>

Here are only HTML and CSS, and it won't display anything, so don't be worried. Let's break it down little by little.

In <template>, there are two elements nested. The outer is wrapper, it is a div without any height because we will use padding-bottom to control its verticle space occupation in the next step. Also position: relative tells the child element that their position should be relative to the left-top corner of the wrapper. Here is a useful link that tells how the position works: mdn-css-postion

The child element, canvas, is where we draw the pixels. Its CSS attribute, position: absolute, tells that it will be placed according to its parent element, which has the position: relative attribute, in our case, it would be wrapper. The following top, bottom, left, and right tells the canvas would be fixed at the left-top corner of the wrapper.

Finally, to make the canvas a cover, we use z-index to set it at the front.

5. Finish <ImageBlur> component

In this step, we will define the properties of the component, so that we don't need to hardcode any Blurhash string in our component. Also, we will learn how to decode a Blurhash string and draw it on the canvas. Here is the code.

ImageBlur.vue
vue
<template>
  <div class="wrapper" :style="`padding-bottom: ${props.aspectRatio}%`">
    <canvas class="canvas" ref="canvas" width=32 height=32 />
  </div>
</template>

<script setup lang='ts'>
  import { decode } from "blurhash"

  // Define the props of this component
  // hash: the blurhash string from Blurhash, with default value "LVKmwwp{krRj8_xsg4Six]xtWCt6"
  // aspectRatio: height/width * 100
  const props = defineProps({
    hash: { type: String, default: "LIA_9M%3L~-.yYRO8{OGuPX8ITo#" },
    aspectRatio: { type: Number, default: 56.25 },
  })

  // use ref to get the canvase element
  const canvas = ref<HTMLCanvasElement | null>(null)
  const pixels = decode(props.hash, 32, 32)

  onMounted(() => {
    if (!canvas.value) {
      // ensure to get the ref of the canvase element
      console.log("Canvase is not ready")
      return;
    }
    const ctx = canvas.value.getContext("2d")
    if (ctx) {
      // ensure ctx is not null
      const imageData = ctx.createImageData(32, 32)
      imageData.data.set(pixels)
      ctx.putImageData(imageData, 0, 0)
    }
  })
</script>

<style scoped>
  /* ... */
</style>

Let's have a look at the script part. We import the decode function for decoding the hash to pixels. Then we define two props hash and aspectRatio. The details are written in the comment.

We use ref to get the DOM element in HTML, just like getElementById() in Vanilla JS.

After that, we use the decode function and store the pixel information in pixels. As long as the component is mounted, we draw the pixel on the canvas.

✨Note that the variable's name should be the same as the ref's name in HTML

Place it in a div and we got this.

vue
<template>
  <div style="width: 300px; height: 200px;">
    <ImageBlur>
  </div>
</template>

6. Composition

Now, let's put everything together!

ImageBg.vue
vue
<template>
  <div ref="wrapper" class="container">
    <ImageBlur class="blur" :hash="props.hash" :style="{ opacity: onLoaded ? 0 : 100 }" />
  </div>
</template>

<script setup lang='ts'>

const props = defineProps<{
  src: string
  hash: string
}>()

const wrapper = ref(null)
const onLoaded = ref(false)

onMounted(() => {
  // Load the bg-image as Image firstly
  const bgImage = new Image()
  bgImage.src = props.src

  // when image full loaded, pass it to bg-image
  bgImage.onload = () => {
    wrapper.value.style.backgroundImage = `url(${props.src})`
    onLoaded.value = true
  }
})
</script>

<style scoped>
.container {
  /** ... */
}

.blur {
  min-width: 100%;
  min-height: 100%;
  object-fit: cover;
  transition: opacity 500ms;
}
</style>

To control the opacity of the blur layer, we define a variable, onLoaded, with an initialized value of false, which means the image is not loaded at the beginning.

After the image is loaded, it will change to true, as we defined this action in bgImage.onload() callback function.

In the style definition of <ImageBlur>, we use the Conditional operator to manipulate the opacity.

Furthermore, we could define the opacity transition in 500ms, to have a smooth transition from blur to clear.

Finally, you have your ImageBg with blur effect. Voila 🎉

Thanks for reading!

✨BonusIf you want the round corner like the image above, try to add border-radius and overflow to the container's CSS
ImageBg.vue
vue
<template>
  <!-- ... -->
</template>

<script setup lang='ts'>...</script>

<style scoped>
.container {
  /* ... */
  border-radius: 2rem;
  overflow: hidden;
}

.blur {
  /* ... */
}
</style>
Designed & Coded by Tianyu @2022~2024