Build An Animated Image Search with Vue.js (Solution to Code Challenge #8)
Publikováno: 18.4.2018
Yet to take the code challenge #8 to build an animated image search? You can still do so! Send in your solutions using the co...
Yet to take the code challenge #8 to build an animated image search? You can still do so! Send in your solutions using the comment section under the post, via Twitter with the hashtag #scotchchallenge or via the Slack channel #codechallenge in the Slack group.
In this post, we shall be solving the challenge by building an animated image search using Vue.js. While images are sourced from Pixabay, simple CSS animation and an amazing technique are employed to move the search bar to the top of the screen once the images load.
As an MVVM framework, Vue was chosen as DOM manipulation is pretty seamless also utilizing it's lifecycle methods are quite straightforward to grasp.
The Base
For this challenge, the base code comprising of HTML, CSS, and JavaScript was provided. This base script simply provides the base structure of the document and required styling.
The HTML
The HTML document created consists of markup for the search form and the empty section to display the images once loaded. Bulma classes were employed to style various elements of the document including the input area and the search button.
<div class="hero is-fullheight is-bold is-link" id="app">
<!-- SEARCH FORM -->
<form id="search-form">
<input
type="text"
class="input is-medium"
placeholder="What images would you like to see on Pixabay?">
<button
type="submit"
class="button is-link is-medium">
Search
</button>
</form>
<div class="hero-body">
<div class="container">
<!-- SEARCH RESULTS GO HERE-->
</div>
</div>
</div>
The CSS
The Sass variant of CSS was used to style the page. This styling is simply to define the color scheme of the page as well as dimensions of elements including the input element.
#search-form {
padding-left: 20%;
padding-right: 20%;
background: #363636;
min-height: 80px;
height: 100vh;
display: flex;
align-items: center;
input {
margin-right: 5px;
}
}
The JavaScript
For this challenge, the use of an API to fetch the images to be displayed is required and an API endpoint was gotten from Pixabay with which all image requests will be made. Also, a sample request URL is shown in the JavaScript code.
const apiUrl = 'https://pixabay.com/api'
const apiKey = '8653965-67fc8570b61c58e735d9adade'
const searchQuery = 'dog'
const sampleRequestUrl = `${apiUrl}/?key=${apiKey}&q=${searchQuery}s&image_type=photo&per_page=15&safesearch=true`;
GET requests will be made to this API endpoint to receive queried images.
The Technique
In this challenge, Vue will be used to request for the images and pass these images to DOM. Moving the search box to the top of the document during a search is done by reducing the height of the search form. CSS animation is used to animate this reduction in size and it appears as though the search box is moved to the top of the viewport.
Create Vue Instance and State Data
In the JavaScript file, we create a new Vue instance which will be mounted using the el
property on the DOM element with the ID of app
.
new Vue({
el: '#app',
data() {
return {
apiUrl: 'https://pixabay.com/api',
apiKey: '8653965-67fc8570b61c58e735d9adade',
images: null,
isSearching: false,
query: ''
}
},
}
Once the Vue instance is created, the el
property is used to mount the instance and data method is created which returns an object. In the data object returned, we pass the apiUrl
as well as the apiKey
and their respective values in the object. State data created are images
, isSearching
, and query
. The images variable is to house the images returned from the search, isSearching
is used to save the state of the app which could either be searching for images or not, these are represented by true
and false
respectively. The query
property contains the search query.
Create search method
Vue's methods
property is used to specify the search method, this method will be called once the search button is clicked. Axios, a promise based HTTP client is used to make API calls to the specified endpoint. In the Vue instance, we add the methods property just after the data
function with:
[...]
methods: {
search() {
if (this.query) {
this.images = [];
this.isSearching = true;
const searchQuery = encodeURIComponent(this.query);
axios.get(`${this.apiUrl}/?key=${this.apiKey}&q=${searchQuery}s&image_type=photo&per_page=15&safesearch=true`)
.then(res => {
if (res.data.total != 0) {
this.images = res.data.hits
this.isSearching = false;
} else {
this.isSearching = false;
}
})
}
}
}
From the script above we see that once search()
method is called, an if statement verifies that query
holds value. The images
property is assigned an empty array, the value of isSearching
is set to true and the encodeURIComponent
method is used to encode the query, incase there are special characters in the query string.
Axios is used to make a GET request to the API endpoint and on the resolution of the promise, an if statement is used to verify the number if images returned as seen in the total
property of the response.
If images are found, the array of images returned is assigned to the images
property and the isSearching
property is set to false
to signify the end of the search operation. If no images were returned we simply change the state of the isSearching
property to false
.
Next up, shall proceed to pass these data to the DOM.
Pass Vue Data to the DOM
In passing the data to the DOM, the two areas of concern are the search form and the search result sections. In the search form, the v-model
directive is used to establish a two-way data bind of the input field to the query
property. The v-on
directive is used to listen to the submit event on the form which in turn calls the search()
method and also, the class of the form is set dynamically using the state of the images
property.
Lastly, the button text is set dynamically and conditionally using Vue and ternary operators. For the search form we now have:
<!-- SEARCH FORM -->
<form id="search-form" @submit.prevent="search" :class="{ 'has-searched': images }">
<input
type="text"
class="input is-medium"
placeholder="What images would you like to see on Pixabay?"
v-model="query">
<button
type="submit"
class="button is-link is-medium">
{{ isSearching ? 'Searching' : 'Search' }}
</button>
</form>
In the search results section, a div is created once the images
property holds data. In this div, the list rendering v-for
directive is used to render each image in the array of fetched images. To render each image, the src
property is set dynamically using the image URL fetched. Lastly, a default status text is rendered conditionally if no images were returned after the search was completed. For the search results section, we now have.
<!-- SEARCH RESULTS -->
<div v-if="images">
<div class="images columns is-multiline" v-if="images.length">
<div class="column is-4" v-for="image in images">
<img class="image" :src="image.webformatURL">
</div>
</div>
<h3 class="title has-text-centered is-1" v-if="!images.length && !isSearching">???? No results found!</h3>
</div>
At this point, the search system works but the results are not visible, how do we make them visible? Using CSS, we add height properties to the has-searched
class which is assigned the search form once images
are loaded. Creating the class style we have:
#search-form.has-searched {
height: 10vh;
}
At this point we are able to see the search results, but it still doesn't look nice with the instant movement of the search box to the top of the viewport. We fix this by introducing the CSS transition
property in the .search-form
ID style. The transition property is edited to:
transition: 1s cubic-bezier(.7,.28,.47,1.15) height;
With this, we have completed the challenge and everything works smoothly. You can see the final product here.
Conclusion
In this post, we solved the challenge by building an animated search using Vue.js. CSS animation, as well as positioning techniques were employed to displace the search bar to the top of the viewport. Feel free to leave your comments and feedback under this post and let's look forward to the next challenge. Happy coding!!