Lazy loading Background Images

Claus Straube
The Startup
Published in
5 min readFeb 4, 2020
A really pretty background image for your app (47.479693, 10.958209). Ok — you can use your own, too ;)

You’re writing a app with VueJs / Vuetify and you want a background image that doesn’t scroll with the content and has lazy loading capabilities? Then this article is for you.

Of course you can use CSS for your background image. And of course you can do all things I’ll show in this article with CSS, too. If you’re a CSS expert… Or if you’re a Google search and copy & paste expert. I’m more or less the latter one. So I thought to myself: “Can’t I do this background image thing with native Vuetify tools as well?” The short answer is — yes you can.

The customer wants a modern looking single page application with a background image. So far so good. The problem starts with content, that is higher than the screen. So you have to scroll. And if you scroll down very far, your beautiful background image will disappear on the top of your screen. An other issue with plain old background images is, that they’re potentially big if the quality is high. That’s bad for your lighthouse audit scores… and for the visitors with low bandwidth. So why not use the Vuetify image component?

<v-img></v-img>

On of the coolest features of this component is the lazy loading image. Instead of loading the full blown high quality image, you can load first a small blurry picture and then the bigger one. So the page visitor sees something, before getting the end result.

v-img lazy loading image

To start with your Vuetify background image app, you can use the VueJS CLI. Go to your development folder, open a terminal and start typing:

vue create my_bg_app
cd my_bg_app
vue add vuetify
npm run serve

You can leave the defaults everywhere. I’m using Typescript, but this should not really make a big difference here. You will end up with a running VueJs / Vuetify demo application. I added a footer and painted the application bar black, but you can leave your app as it is.

First step: Define the app frame. Switch to the src folder and open the App.vue file. You can find there an element called

<v-app-bar></v-app-bar>

It’s important to know the height of the app bar. So you should set the corresponding attribute [1]:

<v-app-bar
app
color="black"
height="100" [1]
dark
>
...
</v-app-bar>

If you’ve added a footer you should set its height [1], too:

<v-footer 
app
color="black"
height="47" [1]
>
...
</v-footer>

Second step: Modify the content component. Go to the components folder and open HelloWorld.vue. You can delete everything inside the template element. You should end with something like this:

<template>
</template>
<script lang=”ts”>
import Vue from ‘vue’
import { Component } from ‘vue-property-decorator’
@Component
export default class Helloworld extends Vue {
}
</script>

If you don’t use Typescript, your script section looks different, but as I wrote above, that doesn’t really matter.

Third step: Add the background image.

<template>
<v-container fluid class=”pa-0"> [1]
<v-img
:src=”require(‘@/assets/bg_image.jpg’)” [2]
:lazy-src=”require(‘@/assets/bg_image_blur.jpg’)” [3]
:height=”bgHeight” [4]
>
</v-img>
</v-container>
</template>
<script lang="ts">
import Vue from 'vue'
import { Component } from 'vue-property-decorator'
@Component
export default class Helloworld extends Vue {
get bgHeight() {
return this.$vuetify.breakpoint.height - 147 [5]
}
}
</script>

First we’re filling our content section with a container [1]. The pa-0 class ensures, that this container has no padding. So it really fills the whole area with no space — if there is something in it, that is big enough. Inside the v-container we’ll place the v-img element. The src [2] and lazy-src [3] attributes are responsible to load the images. Of course the images must be dropped into the assets folder before saving. Now we need a flexible height. If the user changes the browser size, the background image should still fit. For this case Vuetify offers a breakpoint object [5]. You can use this inside a computed property (if you’re not using Typescript the script section is slightly different, but the return statement should be the same). You can reach the height of the screen with this.$vuetify.breakpoint.height. To get the image height, subtract the height of your app bar and footer (if you have one). In our case this is 100 (app-bar)+ 47 (footer) = 147. finally we have to fill the height attribute dynamically with the calculated value of height [4].

Fourth step: Add a scroll container. E.g. a v-sheet element, but it should work with a v-card, too. Place this v-sheet inside the v-img.

<template>
<v-container fluid class=”pa-0">
<v-img
...
>
<v-sheet
color=”transparent” [1]
class=”overflow-y-auto” [2]
:max-height=”bgHeight” [3]
>
</v-sheet>
</v-img>
</v-container>
</template>

We want to see the background image (of course), so we have to set the color attribute to transparent [1]. The class overflow-y-auto [2] adds the vertical scrolling ability to the component, if there is something inside that is higher than the maximum height. So it’s crucial to set the max-height attribute. To hold the background image in an absolute position, the maximum height must be identically to the image height. So we can use here the computed bgHeight property.

Fifth step: Add really long content. We want to scroll. So we need something, that is for sure longer than the screen high. A v-list with 100 items should do that job. If you have an extraordinary high Monitor, set the number to 1000 [1].

<template>
<v-container fluid class="pa-0">
<v-img
...
>
<v-sheet
...
>
<v-row no-gutters align="start">
<v-col key="1" cols="2"></v-col>
<v-col key="2" cols="8">
<v-card
class="my-10"
>
<v-toolbar ...>...</v-toolbar>
<v-toolbar ...>...</v-toolbar>
<!-- a really long list -->
<v-list>
<v-list-item
v-for="i in count"
:key="i"
dark
>
<v-list-item-icon>
<v-icon class="black--text">mdi-file</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>
Foo Item {{i}}
</v-list-item-title>
<v-list-item-subtitle>
The subtitle for the... Foo
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action >
<v-icon>mdi-cloud-download</v-icon>
</v-list-item-action>
</v-list-item>
</v-list>
</v-card>
</v-col>
<v-col cols="2" key="3"></v-col>
</v-row>
</v-sheet>
</v-img>
</v-container>
</template>
<script lang="ts">
import Vue from 'vue'
import { Component } from 'vue-property-decorator'
@Component
export default class Helloworld extends Vue {
// enter a lower number, to get a shorter list
count: number = 100 [1]
get bgHeight() {
return this.$vuetify.breakpoint.height - 147
}
}
</script>

Now you should see something like this:

Really long content with absolute background image.

You can find the sources on a github repository. I hope you have enjoyed this article. Please leave a comment if you have any question or would like to leave feedback.

--

--