Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prefetch an image using Vue's built-in mechanism

I have an app with the Vue CLI. When the app loads, I have a bunch of images that appear with a transition when a user clicks a button. The problem is that when the user clicks a button, the corresponding image only then starts to load, meaning that most of the animation is done until then. This makes the experience quite choppy because the images suddenly pop in during the transition, displacing other elements. I want to prefetch them when the site loads.

This answer suggests using the Image class. However, according to the Vue CLI docs, Vue internally uses its own plugin for that, preload-webpack-plugin, and it apparently can be configured.

I tried to configure it so that it preloads images:

vue.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');

module.exports = {
  configureWebpack: {
    plugins: [
      new HtmlWebpackPlugin(),
      new PreloadWebpackPlugin({
        rel: 'prefetch',
        as (entry) {
          if (/\.css$/.test(entry)) return 'style';
          if (/\.woff$/.test(entry)) return 'font';
          if (/\.png$/.test(entry)) return 'image';
          return 'script';
        }
      })
    ]
  }
}

This only manages to screw up the final index.html, leaving it without the build scripts and styles inside.

If I remove this line:

      new HtmlWebpackPlugin(),

The site still loads but the images are not prefetched. It's the same as if I never did anything in the vue.config.js file.

How do I set it up correctly?


Edit: In Vue components, I use require() to load the images, meaning they pass through Webpack. For example:

<img :src="require('../assets/img/' + img)" draggable="false">

Edit: I was able to prefetch the images as Roy J suggested in the comments:

PreloadImages.vue in my main component:

<template>
  <div style="display: none;">
    <img :src="require('../assets/img/foo.png')">
    <img :src="require('../assets/img/bar.png')">
    <img :src="require('../assets/img/baz.png')">
  </div>
</template>

However, that's not the answer to my actual question - it doesn't use resource hints via <link> tags. It also requires more effort and I believe it's a bad practice.

like image 735
dodov Avatar asked Jan 14 '19 20:01

dodov


2 Answers

Since the plugin is already included by VueJS I think you have to modify it with chainWebpack.

According to the preload webpack plugin documentation, you should also set include option to 'allAssets' value.

It is very common in Webpack to use loaders such as file-loader to generate assets for specific types, such as fonts or images. If you wish to preload these files as well, you can use include with value allAssets

So the configuration will be something like this:

// vue.config.js
module.exports = {
    chainWebpack: config => {
        config.plugin('preload').tap(options => {
            options[0].as = (entry) => {
                if (/\.css$/.test(entry)) return 'style';
                if (/\.woff$/.test(entry)) return 'font';
                if (/\.png$/.test(entry)) return 'image';
                return 'script';
              }
            options[0].include = 'allAssets'
            // options[0].fileWhitelist: [/\.files/, /\.to/, /\.include/]
            // options[0].fileBlacklist: [/\.files/, /\.to/, /\.exclude/]
            return options
        })
    }
}

With a fresh Vue-cli installation I got the following HTML generated

<!DOCTYPE html>
<html lang=en>

<head>
    <meta charset=utf-8>
    <meta http-equiv=X-UA-Compatible content="IE=edge">
    <meta name=viewport content="width=device-width,initial-scale=1">
    <link rel=icon href=/favicon.ico> <title>vue-preload-images</title>
    <link href=/css/app.0b9b292a.css rel=preload as=style>
    <!-- Here is the logo image -->
    <link href=/img/logo.82b9c7a5.png rel=preload as=image>
    <link href=/js/app.30d3ed79.js rel=preload as=script>
    <link href=/js/chunk-vendors.3d4cd4b5.js rel=preload as=script>
    <link href=/css/app.0b9b292a.css rel=stylesheet>
</head>

<body><noscript><strong>We're sorry but vue-preload-images doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript>
    <div id=app></div>
    <script src=/js/chunk-vendors.3d4cd4b5.js> </script> <script src=/js/app.30d3ed79.js> </script> </body> </html>

I hope it will work for you.

like image 161
lbineau Avatar answered Oct 22 '22 02:10

lbineau


Solution 1.

When the user clicks the button you render your image in a non-visible state.

On image's load event you perform your transition smoothly.

If the loading of the image typically takes more than 2 seconds, consider giving the user a visual clue the button click was recorded and stuff is about to happen.

This would be the technically correct solution.


Solution 2.

An alternative, used extensively in production environments, (and having nothing to do with Vue per-se), is to load thumbnails of the images in your page (as in 10 times smaller - which is ~100 times smaller in size, being areas). It might not be obvious, but they make very good substitutes for large ones, while the transition is ongoing - you might also want to experiment with CSS blur filter on them.

The thumbnail and the large image have to be perfectly overlapped, in the same transitioning parent, with the large one underneath. On load event of the large version, fade the thumbnail out, causing a focus-in effect: a subtle eye-catcher.
A rather funny perk of this method is that, if you leave the thumbs on top (with opacity: 0), whenever someone tries to download an image they right click the thumb and have trouble understanding why they're downloading it at such a low res.

Everything is pretty much the same (in terms of animations) with the addition of a focus-in effect on the image, when it actually loaded.

Not DRY, but efficient. Looks professional, everything seems to load instantly and smoothly. Overall, it converts way more than other "correct" solutions. Which is why it's commonly used on websites where page performance is important.


When dealing with visuals, being perfect is looking perfect (and, in UX, feeling/seeming perfect).
Ancient greeks bent their columns so they looked straight when watched from below.

like image 1
tao Avatar answered Oct 22 '22 01:10

tao