DEV Community

Cover image for Building Optimised Image Gallery With Fauna
FesserBlondie
FesserBlondie

Posted on

Building Optimised Image Gallery With Fauna

A quick outline of what we’ll cover within this tutorial:

  • Introduction to what image optimization is?
  • Explain different techniques and methods to optimize image gallery applications.
  • Build a React component using the HTML5 tags to increase accessibility on the images, and also implement Lazy Loading Technique
  • Introduction to Fauna
    • How to setup our database on Fauna and integrate it into our frontend application using the Fauna JavaScript Driver
  • Perform CRUD requests to our database to make our image gallery dynamic.
  • Use cases for Fauna services.

Typical issue developers face when building a web application, like an image gallery application, is how to optimize large file sizes to gain faster load time on a web application while maintaining the image quality and reducing the application's overall performance. Also, the possible issue of accessibility and usability on the images in the web application.

We’ll cover different approaches that would provide feasible solutions to these common issues within this tutorial. We will explore the other solutions ranging from image compression to using the appropriate HTML5 elements, which would solve the accessibility and usability issue, then to using third-party services. Then we’ll go-ahead to implement a simple Image Gallery component using HTML5 elements, also applying the Lazy Loading Technique discussed later in this tutorial.

Prerequisites

This tutorial requires that you have experience with front-end development, experience in using React, and React hooks to build frontend applications.

Introduction To Image Optimisation

Image optimization is simply the act of decreasing file size without losing its quality. Reducing the file size of images using any available technique, either through a third-party plugin, script, or otherwise, speeds up the web application's load time. The main target of image optimizing is the large image files that slow down your web application, creating a less than optimal user experience. To find the delicate balance between the smallest file size and a well desired—acceptable image quality, following the best image optimization techniques will help keep our file size small and optimal enough.Image optimization is also about image SEO, making the images—either the product images and decorative images, to rank more on Google and other image search engines improving the website’s components visibility, as Google uses page load time as a ranking factor in their algorithm.

Next, let’s discuss the different techniques in Image optimization.

Image Compression:

The first technique that we’ll discuss is image compression. Image compression involves minimizing the file size in bytes of a graphics file without degrading the quality of the image to an unacceptable level. The reduction in file size allows more images to be downloaded and stored in a given amount of available disk or memory space.

While compressing images to reduce the file size is a feasible solution, this can drastically reduce the image quality, which isn’t a good experience for our web application. Distorted, blurry images, and greyish image effects are possible outcomes of excessive image compression.These images can’t be used as product images or even decorative images within our web application.

Three common file types are used to post images to the web: JPEG, GIF, and PNG, and compression happen differently for each of them. Choosing and compressing the right file types helps to maintain the quality of the compressed image. The JPEG format allows decent quality at a low file size after compression than other image formats. It will be advisable to compress the JPEG image format more than other formats for it will still maintain good quality unlike others. TinyPNG is an excellent example of an image compressing tool available on the web.

Choose the right file type

When working with images within your web application, choosing the right image format for the right use case helps the web application's performance. There are mainly JPEG, SVG, PNG—PNG-8 and PNG-24, and GIF file formats for image files. These different image formats have different use cases when building your web application, and appropriate usage helps SEO and the overall performance of the web application. JPEG file formats are more suitable for use when adding non-product images to the web application. Images with the JPEG file format provide the best quality for the smallest file size and isbeing the oldest file format, and they are well supported by older browsers and on the new ones. The SVG file formats are vector images that are very lightweight in file size and are best used for icons, shapes and also good for image animation. The SVG file formats are vector images that are very lightweight in file size and used for icons, shapes, and suitable image animation. The SVG files have good support on the browser. The PNGs are new formats of image files ideal for product images when building e-commerce web applications. PNGs also excel as simple decorative images making them an excellent alternative to JPEGs. PNG-24 file formats are up to 3x larger than the PNG-8 file formats. It's safer to use the PNG-8 over the PNG-24 file format. GIF file formats are well designed for animated images, making them the best use for animation on web applications.

Manually Resizing Image File Size

Another safer way to optimize images for the web is by resizing them before using them on our web application.Working with raw images with unnecessary parts that are not needed can be cropped out to reduce file sizes Images taken by the camera would often contain unnecessary portions, and these can be removed through cropping the image with Adobe Photoshop or a similar tool. Using Adobe Photoshop,you can reduce image file size by using the "Save for Web" command in Adobe Photoshop. When using this command, you want to adjust the image to the smallest file size possible while keeping an eye out for image quality.

Using HTML5 Tags

This is a more robust method of image optimization on the web. These are built-in browser capacities for handling images for better performance on the web:

The HTML5 <img> element is designed to give developers the full ability to optimize the images according to the screen resolution of the viewing device. We instruct the browser using these two attributes, srcset and sizes, to decide which image size is needed.

The <picture> tag is another way to provide alternative sources for image files so that the browser can choose depending on device capabilities. It’s used to give flexibility to the web developers to specify different image resources. The <picture> tag contains <source> tag and <img> tag as child elements. The attribute value is set to load a more appropriate image. The <img> element is used for the last child element of the <picture> declaration block. Read more on using <picture> element on MDN.

Progressive image loading

It is a known technique to effectively and smartly load the images of our web application by demand, using small placeholders. In contrast,the original image is loaded behind the background. A progressive image starts off with a low-resolution and progressively enhances itself over time. This method helps the user experience your web application by preventing users with a slow network connection from leaving your site because it displays a white screen for a long time.

Lazy loading the images

Lazy loading helps eliminate the browser from downloading all the images when loading the page. Only download the essential images, and make a lazy loading of the other resources. Chrome and Firefox both support lazy-loading with the loading attribute. We'll add this attribute to <img> elements, and also to <iframe> elements. A value of lazy will tell the browser to load the image immediately if it is in the viewport and also fetch other images when the user scrolls near them. This image optimization method depends on the two DOM events, namely the scroll and resize events.

Build React Components Using The HTML5 Tags

We will implement a simple component structure to add images to your web application. We’ll build this component, ensuring that Accessibility and Usability is well implemented for optimal performance for SEO on the web application.

We’ll use the <img> element for its great support on all the browsers, both old and new browsers. Let’s start with the simplest structure of the <img> element:

import react from "react";

const Image = ({imgLink}) => {
  return (<img src={imgLink} />)
}

export default Image;
Enter fullscreen mode Exit fullscreen mode

The above gives us the bare minimum need to add an image element to our web application. But there are some shortcomings with the above code that I will handle next. These shortcomings are centred around accessibility and usability for images that are naturally inaccessible to people who are unable to see them. Our first line of action would be to make it available to users using screen readers to navigate through our web application. Adding an alt attribute will cause the image accessible for users who use a screen reader to navigate through our web application:

import react from "react";

const Image = ({imgLink, altText}) => {
  return (<img src={imgLink} alt={altText} />)
}

export default Image;
Enter fullscreen mode Exit fullscreen mode

The above code provides a means for us to add descriptive text describing our image through the alt attribute. Providing an alternative means for screen reader users with a description of the image element. The title offers similar capacity but mainly for users using the mouse to navigate the web application:

import react from "react";

const Image = ({imgLink, altText}) => {
  return (<img src={imgLink} alt={altText} title={altText}/>)
}

export default Image;
Enter fullscreen mode Exit fullscreen mode

Adding title attributes to images provides further supporting information if needed to mouse users.

A more efficient way of providing caption or description to an image is through the use of <figure> and <figcaption> HTML5 elements. They are designed to provide a semantic container for figures and to link the figure to the caption. The above code structure will be rewritten like:

import react from "react";

const Image = ({imgLink, altText, additionalInfo}) => {
  return (
    <figure>
      <img src={imgLink} alt={altText} />
      <figcaption>{additionalInfo}</figcaption>
    </figure>)
}

export default Image;
Enter fullscreen mode Exit fullscreen mode

We use the <figcaption> element to provide additional caption/description to the browsers and assistive technology that this is the information to describe the other content within the <figure> element. Also, note that a <figure> could be used for several images.

The above code structure would help us implement a unit image component for any web application with images. Next, we look at using Intersection Observer API to implement Lazy Loading on images.

Implement Lazy Loading With Intersection Observer API

With Intersection Observer API, we can easily observe and detect when an element enters the viewport and takes action.This is very efficient for we are conditional rendering our images or contents only when the user scrolls the image into the viewport. So only the needed contents, the images, are loaded when the user wants to see them.

We’ll demonstrate this with a basic structure of what an image gallery would look like—Let’s give an overview of the markup structure:

<img src="https://images.unsplash.com/photo-1622495546876-3fccb94d3e2c?ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80" />
<img src="https://images.unsplash.com/photo-1622495550744-dd25b569e6c0?ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80" />
<img src="https://images.unsplash.com/photo-1623973676476-a9152edbe397?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=590&q=80" />
<img class="lazy" data-src="https://images.unsplash.com/photo-1619625713173-fce47ce8d960?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80" />
<img class="lazy" data-src="https://images.unsplash.com/photo-1622495893617-38b112b30790?ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=799&q=80" />
<img class="lazy" data-src="https://images.unsplash.com/photo-1623894201770-efa76ccc00f8?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80" />
<img class="lazy" data-src="https://images.unsplash.com/photo-1608790672303-e6931dbff2d8?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1500&q=80" />
<img class="lazy" data-src="https://images.unsplash.com/photo-1623851467157-5eb7199d0f18?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=668&q=80" />
<img class="lazy" data-src="https://images.unsplash.com/photo-1623929147453-b0abf89a9363?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=667&q=80" />
<img class="lazy" data-src="https://images.unsplash.com/photo-1623885065412-300800bcadb3?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=751&q=80" />
Enter fullscreen mode Exit fullscreen mode

The above are sample images that would be loaded into the browser, but we’ll control how we’ll load them into the browser using a script written with the help of the browser’s Intersection Observer API:

document.addEventListener("DOMContentLoaded", function() {
  var lazyloadImages;    

  if ("IntersectionObserver" in window) {
    lazyloadImages = document.querySelectorAll(".lazy");
    var imageObserver = new IntersectionObserver(function(entries, observer) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          var image = entry.target;
          image.src = image.dataset.src;
          image.classList.remove("lazy");
          imageObserver.unobserve(image);
        }
      });
    });

    lazyloadImages.forEach(function(image) {
      imageObserver.observe(image);
    });
  } else {  
    var lazyloadThrottleTimeout;
    lazyloadImages = document.querySelectorAll(".lazy");

    function lazyload () {
      if(lazyloadThrottleTimeout) {
        clearTimeout(lazyloadThrottleTimeout);
      }    

      lazyloadThrottleTimeout = setTimeout(function() {
        var scrollTop = window.pageYOffset;
        lazyloadImages.forEach(function(img) {
            if(img.offsetTop < (window.innerHeight + scrollTop)) {
              img.src = img.dataset.src;
              img.classList.remove('lazy');
            }
        });
        if(lazyloadImages.length == 0) { 
          document.removeEventListener("scroll", lazyload);
          window.removeEventListener("resize", lazyload);
          window.removeEventListener("orientationChange", lazyload);
        }
      }, 20);
    }

    document.addEventListener("scroll", lazyload);
    window.addEventListener("resize", lazyload);
    window.addEventListener("orientationChange", lazyload);
  }
})
Enter fullscreen mode Exit fullscreen mode

With the above code, we closely monitor when the user scrolls or resizes the browser. These events’ listeners trigger a function, this function looks for all the images on the page that are currently deferred and, from this image collection, it checks which images are currently in the view-port by calculating their top offset, that’s the current top position of the document and the window’s height. Once an image has entered the viewport, we pick the URL from the data-src attribute if there exists such attribute on the image and move it to the src attribute and the image will load as a result. Find the code and the live demo here.

Introduction To Fauna And How Set-up database on Fauna

Fauna is a flexible, developer-friendly, transactional cloud database delivered as a secure Data API that provides two interfaces: GraphQL and the Fauna Query Language (FQL). Fauna is a unique indexed document system that supports relations, documents, and graphs for unmatched modeling flexibility. It includes functionality to store collections, indexes, and other databases (multi-tenancy). To learn more about Fauna, visit the official documentation. Fauna uses a pre-existing infrastructure to build web applications without usually setting up a custom API server. This efficiently helps to save time for developers, and the stress of choosing regions and configuring storage that exists among other databases, which is global/multi-region by default, are nonexistent with Fauna. All maintenance we need is actively taken care of by engineers and automated DevOps at Fauna.

We’ll go through the steps involved and set up a database on Fauna, ready for use on the frontend part of any application. First, step is creating an account with Fauna or signing into an existing account. We can register for a new account here. Once signed in, the dashboard would look like the below:

altText
Within the dashboard screen, click on the NEW DATABASE button, and the next screen is where we’ll provide any name appropriate for the database that we plan to use within our frontend application. Here we’ll call the sample we’re creating image_gallery, then go ahead to click on the SAVE button.

altText
Next screen after clicking on the SAVE button would be the screen providing us with the button to create a COLLECTION. A Collection is similar to SQL tables containing similar characteristics, e.g. a user collection with information about users in the database. Click on the NEW COLLECTION button to navigate to the form for creating a collection:

altText
Within the collection name field on the form we’ll name our data collection. In this tutorial, we’ll call it image_gallery_collection.

Next we create the INDEXES for the collection of the database. A Fauna index allows us to browse through data stored in a database collection based on specific attributes. On the sidebar of the screen, click on the Indexes link. The next screen contains a NEW INDEX button, click on it to bring out the form for creating an index:

altText

In the above form, select the current collection we’re creating the index for. Then provide the name for the new index and save it.

After creating the above index, it’s time to create our DOCUMENT, this will contain the links/data we’ll use to populate our we application. Click on the NEW DOCUMENT button to get started. With the provided text editor we’ll create document to contain the image links with any extra information:

altText
The above image object represents the unit data we need to create a simple image gallery. Your choice of data can be so different from what we have here, serving the purpose whatever you want it for within your web application. We can create multiple document to server our database purpose. Let’s create two extra:

altText

Now we have a basic set up of a database, let’s look explore how to integrate Fauna into any React frontend application.

Integrating Fauna Into Frontend Application With Fauna JavaScript Driver

Install the dependency, faunadb, through npm or yarn into the React app.

yarn add faunadb 
or
npm install faunadb
Enter fullscreen mode Exit fullscreen mode

The faunadb package is Fauna JavaScript driver for interacting with Fauna API to get data from the database. Next, we set up the faunadb within the application and integrate it into our application.

import faunadb from 'faunadb';
const client = new faunadb.Client({
  secret: process.env.REACT_APP_DB_KEY,
});
const q = faunadb.query;

const getAllImageLinks = client
  .query(q.Paginate(q.Match(q.Ref("indexes/all_image_links"))))
  .then((res) => {
    const linkRef = res.data;
    const getAllDataQuery = linkRef.map((ref) => {
      return q.Get(ref);
    });
    return client.query(getAllDataQuery).then((data) => data);
  })
  .catch((err) => err.message);

const createImageLink = (imageLink) =>
  client
    .query(q.Create(q.Collection("image_gallery_collection"), { data: { imageLink } }))
    .then((ref) => ref)
    .catch((err) => err.message);

const deleteImageLink = (imageLinkId) =>
  client
    .query(q.Delete(q.Ref(q.Collection("image_gallery_collection"), imageLinkId)))
    .then((ref) => ref)
    .catch((err) => err.message);
export { getAllImageLinks, createImageLink, deleteImageLink };
Enter fullscreen mode Exit fullscreen mode

In the above code we created and exported three basic CRUD actions: getAllImageLinks, createImageLink, and deleteImageLink. With these actions, we will create, fetch, and delete the ImageLink data from the database through some react hooks. Within the getAllImageLinks action, we’ll get all the date in the database under the all_image_links index. The all_image_links index groups the image_link_collection data, within the database, into a single group search. The createImageLink action gives us the interface to create more imageLink if the need be, within our Collection. The deleteImageLink takes an ID, looks for the document on the database with that ID and deletes it.

Rewriting our image gallery prototype to contain these action:

import { useEffect, useState } from "react";
import { getAllImageLinks, createImageLink, deleteImageLink } from "./actions";
import Image from "./Image";

const App = () => {
    const [imgLinkArr, setImgLinkArr] = useState([]);

    useEffect(() => {
        getAllImageLinks.then((res) => {
            setImgLinkArr(res);
        });
    }, [imgLinkArr]);

    const createImgLink = (imageLink) => {
        const image = {
            imageLink,
            altText: "Put the description for the image here",
            additionalInfo: "Any caption goes here"
        };
        createImageLink(image).then((res) => {
            console.log("Image has been added to the database");
        });
        setImgLinkArr([...imgLinkArr, image]);
    };

    const deleteImgLink = (id) => {
        const newLinkArr = imgLinkArr.filter((link) => link.ref.id !== id);
        setImgLinkArr(newLinkArr);
        deleteImageLink(id).then((res) => res);
    };

    const extractLinkArr = () => {
        return (
            <>
                {imgLinkArr &&
                    imgLinkArr.map((imgLink) => (
                        <li key={imgLink.ref.id} id={imgLink.ref.id}>
                            <Image
                                imgLink={imgLink.data.image.imageLink}
                                altText={imgLink.data.image.altText}
                                additionalInfo={imgLink.data.image.additionalInfo}
                            />
                            <div>
                                <button type="button" onClick={(e) => deleteImgLink(imgLink.ref.id)} />
                            </div>
                        </li>
                    ))}
            </>
        );
    };

    return (
        <div className="container">
            <h1>A simple demo of integrating Fauna into Frontent Application</h1>
            <div className="image-container">
                <ul className="image-list">{extractLinkArr()}</ul>
                <button type="button" onClick={createImgLink("./cute-cate.jpeg")} />
            </div>
        </div>
    );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

In the above code, we imported all the utility actions for interaction with our database. Within the useEffect react hook, we reach out to the database to get all the items within it; that’s our image documents containing the imageLink field and other fields. Next is the createImgLink function that accepts an image link as its argument, then creates an image object containing the fields necessary to our database. We pass this image object to the utility action, createImageLink, which takes this object and updates our database. The following function, deleteImgLink takes an ID value, looks through our current imgLinkArr array, and deletes the array element with that ID. Then it goes ahead to request the database with that ID to delete any document with the same ID by calling the deleteImageLink utility action passing the ID as an argument. Then the extractLinkArr function takes our imgLinkArr array, looping through it to get the imageLink, altText, and additionalInfo values to update the UI of our application.

Conclusion

We’ve covered two things within this tutorial, how to optimize your images within your web applications. Discussing the different techniques in Image optimization and also giving examples where needed. Next, we covered what Fauna is, how to set up a database on Fauna.Then proceeded to integrate Fauna into our frontend application using the Fauna JavaScript Driver. Make CRUD requests to our database to update our frontend UI.

I will love to see your opinions on the tutorial and any suggestions for the article. Also, love to see what you’ve built with Fauna. Reach out to me via email at, privatefesserblondie@gmail.com, and also through the comment section.

Top comments (0)