Categories
Vue

Blur Web Page Elements Easily with V-Blur for Vue.js

Spread the love

Blurring elements is useful when you want something hidden. For example, it’s handy to blur something for paywalls. With CSS, blurring content is easy with the blur property. However, if you want to change the blurring dynamically, then the blur settings has to be changed by JavaScript. For Vue.js apps, there’s the V-Blur library to help us achieve the dynamic blur effect. This makes changing the blur setting as easy as adding a few lines of code.

In this article, we will make a news reader app which lets users blur and unnlur headline contents. There will be a home page where you can get headlines by country, and a search page for searching headlines by keyword. We will get our content from the News API, located at https://newsapi.org/docs. To start, we will run the Vue CLI by running:

npx @vue/cli create news-app

In the wizard, we select the ‘Manually select features’ and select Vue Router and Babel.

Next we install some packages. We will use Axios for making HTTP requests, BootstrapVue for styling, the Country-List for getting a list of country names and codes, V-Blur for adjusting the blur effects, and Vee-Validate for form validation. To install them, we run:

npm i axios bootstrap-vue country-list v-blur vee-validate

With all the libraries install, we can start writing our news app. First we create an .env file in the project’s root folder and add our API key there. The key of the entry should be VUE_APP_APIKEY and the value should be the API key you got from the News API website.

Next we create amixins folder in the src folder and create a file called requestsMixin.js file. In there, we add:

const APIURL = "https://newsapi.org/v2";
const axios = require("axios");

export const requestsMixin = {
  methods: {
    getHeadlines(country) {
      return axios.get(
        `${APIURL}/top-headlines?country=${country}&apiKey=${process.env.VUE_APP_APIKEY}`
      );
    },

    getEverything(keyword) {
      return axios.get(
        `${APIURL}/everything?q=${keyword}&apiKey=${process.env.VUE_APP_APIKEY}`
      );
    }
  }
};

This file has the code to get the headlines by country and keyword from the News API.

Next in the views folder, we replace the code in the Home.vue file with:

<template>
  <div class="page">
    <h1 class="text-center">Headlines</h1>
    <ValidationObserver ref="observer" v-slot="{ invalid }">
      <b-form @submit.prevent="getHeadlinesByCountry" novalidate>
        <b-form-group>
          <ValidationProvider name="country" rules="required" v-slot="{ errors }">
            <b-form-select v-model="country" @change="getHeadlinesByCountry">
              <option :value="c.code" v-for="c of countries" :key="c.code">{{c.name}}</option>
            </b-form-select>
            <b-form-invalid-feedback :state="errors.length == 0">Country is requied.</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
      </b-form>
    </ValidationObserver>

    <b-card
      v-for="(h, i) in headlines"
      :title="h.title"
      :img-src="h.urlToImage"
      img-bottom
      :key="i"
    >
      <b-card-text v-blur="blurConfigs[i]">{{h.content}}</b-card-text>
      <b-button
        variant="primary"
        @click="blurConfigs[i].isBlurred = !blurConfigs[i].isBlurred"
      >Toggle Summary</b-button>
      <b-button :href="h.url" target="_blank" variant="primary">Read</b-button>
    </b-card>
  </div>
</template>

<script>
// @ is an alias to /src
const { getName, getData } = require("country-list");
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "home",
  mixins: [requestsMixin],
  data() {
    return {
      countries: getData(),
      country: "US",
      headlines: [],
      blurConfigs: []
    };
  },
  beforeMount() {
    this.getHeadlinesByCountry();
  },
  methods: {
    async getHeadlinesByCountry() {
      const { data } = await this.getHeadlines(this.country);
      this.headlines = data.articles;
      this.blurConfigs = this.headlines.map(h => ({
        isBlurred: true,
        opacity: 0.3,
        filter: "blur(1.2px)",
        transition: "all .3s linear"
      }));
    }
  }
};
</script>

We have the password form in this component. The form includes name, URL, username, and password fields. All of them are required. We use Vee-Validate to validate the form fields. The ValidationObserver component is for validating the whole form, while the ValidationProvider component is for validating the form fields that it wraps around. The validation rule is specified by the rule prop of each field. The state prop is for setting the validation state which shows the green when errors has length 0 and red otherwise. The error messages are shown in the b-form-invalid-feedback component. This page only has the countries drop down. The data in the drop down is populated with the country-list package we installed.

In the beforeMount hook, we run the getHeadlinesByCountry to get the headlines by running the this.getHeadlines from the requestsMixin, with the initial value for country , which is set to the "US”. Once we get the data, we map them to the default blur config so that we can toggle it in the template. In the template, we have the cards to display the headlines. They are blurred by default. Below the headline, we have a toggle button to toggle the blurring of the news summary for each entry.

Next, we add a Search.vue file in the views folder and add:

<template>
  <div class="page">
    <h1 class="text-center">Search</h1>
    <ValidationObserver ref="observer" v-slot="{ invalid }">
      <b-form @submit.prevent="onSubmit" novalidate>
        <b-form-group label="Keyword">
          <ValidationProvider name="keyword" rules="required" v-slot="{ errors }">
            <b-form-input
              type="text"
              v-model="form.name"
              placeholder="Keyword"
              name="keyword"
              :state="errors.length == 0"
            ></b-form-input>
            <b-form-invalid-feedback :state="errors.length == 0">Keyword is requied.</b-form-invalid-feedback>
          </ValidationProvider>
        </b-form-group>
        <b-button type="submit" variant="primary">Search</b-button>
      </b-form>
    </ValidationObserver>

    <b-card
      v-for="(h, i) in headlines"
      :title="h.title"
      :img-src="h.urlToImage"
      img-bottom
      :key="i"
    >
      <b-card-text v-blur="blurConfigs[i]">{{h.content}}</b-card-text>
      <b-button
        variant="primary"
        @click="blurConfigs[i].isBlurred = !blurConfigs[i].isBlurred"
      >Toggle Summary</b-button>
      <b-button :href="h.url" target="_blank" variant="primary">Read</b-button>
    </b-card>
  </div>
</template>

<script>
// @ is an alias to /src
const { getName, getData } = require("country-list");
import { requestsMixin } from "@/mixins/requestsMixin";

export default {
  name: "search",
  mixins: [requestsMixin],
  data() {
    return {
      form: {},
      headlines: [],
      blurConfigs: []
    };
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
      const { data } = await this.getEverything(this.form.keyword);
      this.headlines = data.articles;
      this.blurConfigs = this.headlines.map(h => ({
        isBlurred: true,
        opacity: 0.3,
        filter: "blur(1.2px)",
        transition: "all .3s linear"
      }));
    }
  }
};
</script>

In this file, we added a form to let users search for news headlines by keyword. We call onSubmit when the user click Search on the form. We get the validation state of the form by using this.$refs.observer.validate(); . The ref refers to the ref of the ValidationObserver . If it resolves to true , then we call the this.getEverything function from the requestsMixin to get the headlines by keyword. Once we get the headlines, we map them to the default blur config so that we can toggle it in the template. In the template, we have the cards to display the headlines. They are blurred by default. Below the headline, we have a toggle button to toggle the blurring of the news summary for each entry.

Next in App.vue , we replace the existing code with:

<template>
  <div id="app">
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand to="/">News App</b-navbar-brand>

      <b-navbar-toggle target="nav-collapse"></b-navbar-toggle>

      <b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/" :active="path  == '/'">Home</b-nav-item>
          <b-nav-item to="/search" :active="path  == '/search'">Search</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <router-view />
  </div>
</template>

<script>
export default {
  data() {
    return {
      path: this.$route && this.$route.path
    };
  },
  watch: {
    $route(route) {
      this.path = route.path;
    }
  }
};
</script>

<style lang="scss">
.page {
  padding: 20px;
}

button,
.btn.btn-primary {
  margin-right: 10px !important;
}

.button-toolbar {
  margin-bottom: 10px;
}
</style>

to add a Bootstrap navigation bar to the top of our pages, and a router-view to display the routes we define.

Next in main.js , replace the code with:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required } from "vee-validate/dist/rules";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import vBlur from "v-blur";

Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
extend("required", required);
Vue.use(vBlur);
Vue.use(BootstrapVue);

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");

so that we add the libraries we installed to our app so we can use it in our components. We call extend from Vee-Validate to add the form validation rules that we want to use. Also, we add the V-Blur library here so we can use it in all our components. We imported the Bootstrap CSS in this file to get the styles.

In router.js , we replace the existing code with:

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
import Search from "./views/Search.vue";

Vue.use(Router);

export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    },
    {
      path: "/search",
      name: "search",
      component: Search
    }
  ]
});

to include our home and search pages.

Finally, in index.html , replace the existing code with:

<!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.0" />
    <link rel="icon" href="<%= BASE_URL %>favicon.ico" />
    <title>News App</title>
  </head>
  <body>
    <noscript>
      <strong
        >We're sorry but v-blur-tutorial-app doesn't work properly without
        JavaScript enabled. Please enable it to continue.</strong
      >
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

to change the title.

After all the hard work, we can start our app by running npm run serve. Finally, we get:

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *