Improving Performance of the Loklak Search with Lazy Loading Images

Loklak Search initially faced a problem of huge load time because of a high number of parallel HTTP requests. Feeds page of the search engine made near to 50 parallel HTTP requests on every new search. One of the possible ways to reduce load time in long pages is to lazily load images. Since loklak is a multi-platform web application, a majority of your site’s visitors might use the application from high-latency devices (i.e. mobiles, tablets), then lazy-loading is required for a smooth user experience (as website speed equals User Experience).

I am explaining in this blog about how I took advantage of a directive to implement lazy loading of images and how the performance of the application improved.

What is lazy loading?

As this project website states, “Lazy loading is just the opposite of ‘pre-loading images’. The basic idea behind lazy loading is to keep a number of parallel request low. The amount of request is kept low until the user scrolls down then the images will load.” This idea is used by Google images to reduce the number of parallel requests.

As we can look in the image below, the amount of parallel request for images sent to the server without lazy loading:

Using viewport directive in loklak to lazily load images

Lazy loading can be implemented in Angular using viewport directive. We need to setup viewport and start using it in feed component to lazy load the profile pictures of the users and the images.

Using viewport directive in Components

  • In this directive, we have two observables :-
  1. Scroll which keeps a check on the scroll position of the page
    this.scroll =
    Observable.fromEvent(window, ‘scroll’).subscribe((event) =>
    {
    this.check();
    });

     

  2. Resize which keeps a check on the resize event when the browser window changes size
    this.resize =
    Observable.fromEvent(window, ‘resize’).subscribe((event) =>
    {
    this.check();
    });

     

Now, whenever the resize and scroll event occurs, it calls a method check which calculates the dimensional parameters of the element and whenever element has entered the viewport it emits an event (which is of type boolean) with value true. check() function takes up reference of the element. Next, it calculates area occupied by this element i.e. elementSize. Next, it looks for the position of the element within the viewport with getBoundingClientRect(). Parameter partial can be set to true if the we need to check if image appears in viewport partially. Moreover, we can specify parameter direction to specify the direction from which the reference element will be entering. Now, we would use conditional statements to check if element exists within the dimensions of viewport and would emit an event.

check(partial:boolean = true, direction:string = ‘both’) {
const el = this._el.nativeElement;const elSize = (el.offsetWidth * el.offsetHeight);const rec = el.getBoundingClientRect();const vp = {
width: window.innerWidth,
height: window.innerHeight
};const tViz = rec.top >= 0 && rec.top < vp.height;
const bViz = rec.bottom > 0 && rec.bottom <= vp.height;const lViz = rec.left >= 0 && rec.left < vp.width;
const rViz = rec.right > 0 && rec.right <= vp.width;const vVisible = partial ? tViz || bViz : tViz && bViz;
const hVisible = partial ? lViz || rViz : lViz && rViz;let event = {
target: el,
value: false
};if (direction === ‘both’) {
event[‘value’] = (elSize && vVisible && hVisible) ? true : false;
}
else if (direction === ‘vertical’) {
event[‘value’] = (elSize && vVisible) ? true : false;
}
else if (direction === ‘horizontal’) {
event[‘value’] = (elSize && hVisible) ? true : false;
}this.inViewport.emit(event);
}

 

  • Next, we need to import viewport directive in our component using this structure:

import { InViewportDirective } from ‘../shared//in-viewport.directive’;

declarations: [

InViewportDirective

]
})

 

  • Create a method in the respective component’s class that would keep a check on the boolean value returned by the directive

public inview(event) {
if (event.value === true) {
this.inviewport = event.value;
}
}

 

In this step we use viewport directive as an attribute directive and the $event value returned would be passed as a parameter to inview method of the component’s class. Here basically, if the feed card comes into viewport (even partially) then the event is emitted and image element is displayed on the screen.Now as the image is displayed, a call is made to receive the images. Control over img is made using *ngIf statement and checks if inviewport is true or false.

<span class=“card” in-viewport (inViewport)=”inview($event)”>
<img src={{feedItem.user.profile_image_url_https}}” *ngIf=“inviewport”/>
</span>

 

The images will load lazily only when the element is in the viewport of the device. Consequently, the amount of parallel image requests sent would decrease and application performance will increase. In fact, not just images but this directive can be used for lazy loading of all media elements in your application.

The image below shows that the reduced amount of image requests sent during the initialization of the feed result when viewport directive is used:-

Resources