Lazy loading videos with plain old javascript
Tags: javascript, html, lazy, video
Published: 2020-04-11 20:19:26

---
title: Lazy loading videos with plain old javascript
published: true
description: Making your own lazy loading is easy. In this tutorial we will see how you can lazy videos with vanilla JS.
tags: #javascript #html #lazy #video
cover_image: https://images.unsplash.com/photo-1530435460869-d13625c69bbf?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=750&q=80
---
While making landing page for [Hoverify](https://tryhoverify.com), I noticed terrible loading times and performance. Scrolling was a laggy until everything got loaded. Since page is filled with videos, loading them at once was a very bad idea so I implemented some lazy loading with some pure JS. This article is for sharing what I learned.  

## What is lazy loading 
Lazy loading is basically loading content at the point when it is needed. In our case we want to load videos when they are completely in the viewport. 

### Let's start
It is mostly javascript in action so there is not much on html and css side but we will start by writing a basic page for testing.
```html
    <html>
		<head>
			<title>Lazy Load Example</title>
		</head>
	  	<style>
			video {
				width: 540px;
				height: 340px;
				margin-bottom: 20rem;
				display: block;
			}
		</style>
		<body>
			  <!--Without lazy load-->
			  <video loop=1 muted=1 autoplay>
				  <source src="https://tryhoverify.com/videos/live_editing.mp4" type="video/mp4"></source>
			  </video>
			<!--With lazy load-->
			<video loop=1 muted=1 autoplay>
				<data-src src="https://tryhoverify.com/videos/live_editing.mp4" type="video/mp4"></data-src>
			</video>
			
			<!--With lazy load-->
			<video loop=1 muted=1 autoplay>
				<data-src src="https://tryhoverify.com/videos/live_editing.mp4" type="video/mp4"></data-src>
			</video>
		</body>

	</html>
   ```
   
 As you can see there are three video tag. Top one is a normal video tag which will be normally loaded. Other two does not have `<source>` tag because we will replace `<data-src>` with `<source>` later when the video is visible in the viewport.
 
### Fun part
Now let's think about the problem for a second. We want to replace  `<data-src>` with `<source>` whenever the video gets in the viewport for the first time. We also need to handle scroll, load and resize events as they will have effect on visibility of elements.

#### Detecting if the video is in the viewport or not
To do this we can use `getBoundingClientRect()` to get the position of element on the screen and compare that to viewport size.
```js
	function isElementInViewport (el) 
	{
	    var rect = el.getBoundingClientRect();

	    return (
	        rect.top >= 0 &&
	        rect.left >= 0 &&
	        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 
	        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
	    );
	}
```

#### Listen to scroll, resize and load events
We will listen to `load`, `scroll` and `resize` event and bind them to a handler function which will loop through all the videos found in the page. It will check if they are in the viewport and do our simple trick of replacing tags so that browser can start loading videos.
```js
	const handler = () => 
	{
		/* 
			Check videos if they are in the viewport.
			If yes then replace data-src with source.
		*/ 
	}

	addEventListener('load', handler, false);
	addEventListener('scroll', handler, false);
	addEventListener('resize', handler, false);
```
1. `load` event is fired when all subframes, images, stylesheets, scripts, etc have been loaded. 
2. `scroll` is fired whenever you scroll.
3. `resize`is fired when you resize the viewport.

#### Looping through videos
Last piece of puzzle is to handle all the events that we added above. As explained above we need to loop through and check if the videos are in the viewport or not.
```js
	const handler = () => 
	{
		const videos = document.querySelectorAll('video');
		for (let i = 0; i < videos.length; i++)
		{
			const video = videos[i];
			const visible = isElementInViewport(video); // Check if the video is in the viewport or not.
			if (visible)
			{
				const dataSrc = video.querySelector('data-src');
				if (dataSrc) // Check if data-src exists or not. If yes, than we have never loaded this video.
				{
					// Creating souce element and adding respective attributes.
					const source = document.createElement('source');
					source.src = dataSrc.getAttribute('src');
					source.type = dataSrc.getAttribute('type');
					
					video.appendChild(source); // Add new source element to video.
					video.removeChild(dataSrc); // Remove data-src from video. 
				}
			}
		}
	}
```
We replaced `data-src` with `video` when video was fully visible in the viewport. But you will see that we also placed a check to see if `data-src` exists or not. This is because we are deleting `data-src` when we replace it and it also saves us extra operations of replacing it again and again. 

### Final javascript code
```js
	function isElementInViewport (el) 
	{
	    var rect = el.getBoundingClientRect();

	    return (
	        rect.top >= 0 &&
	        rect.left >= 0 &&
	        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 
	        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
	    );
	}

	const handler = () => 
	{
		const videos = document.querySelectorAll('video');
		for (let i = 0; i < videos.length; i++)
		{
			const video = videos[i];
			const visible = isElementInViewport(video); // Check if the video is in the viewport or not.
			if (visible)
			{
				const dataSrc = video.querySelector('data-src');
				if (dataSrc) // Check if data-src exists or not. If yes, than we have never loaded this video.
				{
					// Creating souce element and adding respective attributes.
					const source = document.createElement('source');
					source.src = dataSrc.getAttribute('src');
					source.type = dataSrc.getAttribute('type');
					
					video.appendChild(source); // Add new source element to video.
					video.removeChild(dataSrc); // Remove data-src from video. 
				}
			}
		} 
	}

	addEventListener('load', handler, false);
	addEventListener('scroll', handler, false);
	addEventListener('resize', handler, false);
```

You can find final working example [here](https://codepen.io/zicsus/pen/PoPwQaG)

This was a very basic example of lazy loading. You can do much better than this. For instance, you can add some animations or you can optimize event handling for better performance.

Please let me know if you find this useful in comments. 
##### Thanks for reading :)