How to lazy load a videos list in a responsive layout built with SASS and GULP

| In Articles | 2nd December 2019

This article about How to build a videos list with a lazy load system, is a very good reference for how to setup GULP with SASS on a new project. Gulp is loosing land in front of Webpack in the last years, but for small projects is still a very good option and it can be more easly setup and used. It is a lot more easier for beginners to pick up GULP than Webpack especially for small to medium projects.

Some more libraries are used in this project, Velocity-animate is for the lazy load functionality, Fluidvids is to help with making the video player responsive, and the old and well known jQuery.

Responsive videos list with lazy load
A printscreen on how the application is looking like. It also has a details page

As on the file bellow, with Gulp we used a few tasks:

  • browserSync - to automatically refresh the browser after each save, to avoid Pressing F5 or the browser refresh button. If the project is not opened into the browser yet, after it's first compilation, browseSync will open in the browser the link from the proxy
  • html - will also add any html file to the browserSync pipe. In this way, broeser will refresh not only for changes in js or css files but even on html files changes
  • sass - this task will compile the files inserted with the .src method, it will build a sourcemap that will point to the original file for each css style, it will compress (minify) the css, it will save the file to a /dest folder, rename the final file with .min to show that it has minified content and add the file to browserSync stream.
  • lint - this task will use the js custom file, where our custom code will be, to report any syntax errors in case they will be, while writing the code
  • scripts - will give as the possibility to include the .js files we will use and do all the other things we done with the .sass file, as: build a sourcemap, rename, minify (with uglify), generate a combined file and sent it to a destination folder . Always remember, to remove the sourcemap for production (when the project is moved on a live server as it will add a lot to the project size in both .css and .js generated files. It's purpose is only to help with the developing)
  • watch - this is a task used in conjunction with browserSync and is helping with watching for changes once the files are saved, compiling them into the final files. BrowserSync it sees the final files were changed and it refreshing the browser page.
  • default - is the task where all the above tasks are called in a specific order
var gulp 			= require('gulp');
var jshint 			= require('gulp-jshint');
var uglify 			= require('gulp-uglify');
var concat 			= require('gulp-concat');
var sass 			= require('gulp-sass');
var rename 			= require('gulp-rename');
var sourcemaps 		= require('gulp-sourcemaps');
var browserSync 	= require('browser-sync').create();
const stylish 		= require('jshint-stylish');


// ////////////////////////////////////////////////
// Browser-Sync Tasks
// // /////////////////////////////////////////////
gulp.task('browserSync', function() {
    browserSync.init({
        proxy: "http://localhost/tests/youtube-list-json/"
    });
});

// ///////////////////////////////////////////////
// HTML Task
// ///////////////////////////////////////////////
gulp.task('html', function(){
    gulp.src('./*.html')
    .pipe(browserSync.stream());
});

// ///////////////////////////////////////////////
// SASS Task
// ///////////////////////////////////////////////
gulp.task('sass', function () {
    gulp.src('./sass/**/*.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest('./dest'))
		.pipe(rename('all.min.css'))
		.pipe(gulp.dest('./dest'))
		.pipe(browserSync.stream());
});

// ////////////////////////////////////////////////
// JS Lint for errors
// // /////////////////////////////////////////////

gulp.task('lint', function() {
  return gulp.src('./js/custom.js')
    .pipe(jshint())
    .pipe(jshint.reporter(stylish));
});

// ////////////////////////////////////////////////
// Load the scripts
// // /////////////////////////////////////////////
gulp.task('scripts', function() {

	return gulp.src(
			[
				'./js/jquery.min*.js',
				'./js/inview.js',
				'./js/fluidvids.js',
				'./node_modules/velocity-animate/velocity.min.js',
				'./node_modules/velocity-animate/velocity.ui.min.js',
				'./js/custom.js',
			]
		)
		.pipe(concat('all.js'))
		.pipe(gulp.dest('./dest'))
		.pipe(rename('all.min.js'))
		.pipe(uglify()) //use this only on production
		.pipe(gulp.dest('./dest'))
		.pipe(browserSync.stream());

});

// ////////////////////////////////////////////////
// Watch tasks
// // /////////////////////////////////////////////
gulp.task('watch', function(){
	gulp.watch('./sass/**/*.scss', ['sass']);
	gulp.watch('./js/*.js', ['scripts']);	
    gulp.watch("youtube-list-json/*.html").on('change', browserSync.reload);
});

// ////////////////////////////////////////////////
// Default tasks
// // /////////////////////////////////////////////
gulp.task('default', ['watch', 'browserSync', 'sass', 'scripts', 'lint']);

In our custom.js file we created a few functions to help us with functionality and layout

  • text_truncate - will reduce the size of a text to a number of characters that has to be defined into the second parameter of the function
  • getAllUrlParams - will get all the parameters from an url
  • make_youtube_player_responsive - will make the video player responsive
  • return_month_as_name - to convert months as number to word
  • return_formated_date - this function will reformat the date, having the month as a word generated by the above function
  • Bellow this functions we check if the DOM is ready, do an AJAX call to a Youtube playlist, create each video item as an element of the videos list, apply some classes when the element is in View (visible, at load or scroll) and add all the generated content to the body, with lazy load, as seen into the next file

The lazy load videos list code

(function(){
	"use strict";

	//trucate a text based on it's length
	function text_truncate(str, length, ending) {
	    if (length == null) {
	      length = 100;
	    }
	    if (ending == null) {
	      ending = '...';
	    }
	    if (str.length > length) {
	      return str.substring(0, length - ending.length) + ending;
	    } else {
	      return str;
	    }
  	}

  	//get parameter from url
  	function getAllUrlParams(url) {

		// get query string from url (optional) or window
		var queryString = url ? url.split('?')[1] : window.location.search.slice(1);

		// we'll store the parameters here
		var obj = {};

		// if query string exists
		if (queryString) {

		// stuff after # is not part of query string, so get rid of it
		queryString = queryString.split('#')[0];

		// split our query string into its component parts
		var arr = queryString.split('&');

		for (var i=0; i<arr.length; i++) {
		  // separate the keys and the values
		  var a = arr[i].split('=');

		  // in case params look like: list[]=thing1&list[]=thing2
		  var paramNum = undefined;
		  var paramName = a[0].replace(/\[\d*\]/, function(v) {
		    paramNum = v.slice(1,-1);
		    return '';
		  });

		  // set parameter value (use 'true' if empty)
		  var paramValue = typeof(a[1])==='undefined' ? true : a[1];

		  // (optional) keep case consistent
		  //paramName = paramName.toLowerCase();
		  //paramValue = paramValue.toLowerCase();

		  // if parameter name already exists
		  if (obj[paramName]) {
		    // convert value to array (if still string)
		    if (typeof obj[paramName] === 'string') {
		      obj[paramName] = [obj[paramName]];
		    }
		    // if no array index number specified...
		    if (typeof paramNum === 'undefined') {
		      // put the value on the end of the array
		      obj[paramName].push(paramValue);
		    }
		    // if array index number specified...
		    else {
		      // put the value at that index number
		      obj[paramName][paramNum] = paramValue;
		    }
		  }
		  // if param name doesn't exist yet, set it
		  else {
		    obj[paramName] = paramValue;
		  }
		}
		}

		return obj;
	}

	//make the player to extend 100% on width
	function make_youtube_player_responsive(){
	    //making youtube videos responsive
	    Fluidvids.init({
	      	selector: 'iframe',
	      	players: ['www.youtube.com']
	    });
	}

	//convert months number to word
	function return_month_as_name(month_index){
		var months_list = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
		  return months_list[month_index];
		
	}

	//reformat the date
	function return_formated_date(date){			  		
  		//simplify the date
			var date = new Date(
        	Date.parse(date.replace(/ *\(.*\)/,""))
        );		
		return return_month_as_name(date.getMonth()) + " " + date.getDate() + ", " + date.getFullYear();
	}

	//define the array that will keep all the video items
	var htmlElements 	= [],
		json_link 		= 'https://www.googleapis.com/youtube/v3/playlistItems?part=snippet,contentDetails,status&maxResults=10&playlistId=PLSi28iDfECJPJYFA4wjlF5KUucFvc0qbQ&key=AIzaSyCuv_16onZRx3qHDStC-FUp__A6si-fStw';

	$(document).ready(function($){		

		//get the JSON for the listing
	    $.getJSON(json_link,
		{
		  	format: "json"
		}, function(data) {	

	        //print the elements 
			var elements 	= data.items;

		  	$.each( elements, function( key, val ) {
	        
		  		var link 	= 'detail.html?id=' + elements[key].id,
		  			title  	= elements[key].snippet.title,
		  			date 	= elements[key].contentDetails.videoPublishedAt;		  		

		    	htmlElements.push( "<div class='list-element' data-id='" + elements[key].id + "'><div class='item_content'><h3 class='title'><a href=" + link + " title='" + title + "'>" + elements[key].snippet.title + "</a></h3><span class='date'>Published on " + return_formated_date(date) + "</span><p>" + text_truncate(elements[key].snippet.description,200) + "</p></div><a class='img_item' href=" + link + " title='" + title + "'><img src='" + elements[key].snippet.thumbnails.medium.url + "'  alt='" + elements[key].snippet.title + "' /></a></div>" );
		  	});
		 
		 	//append the elements of the array to body
		  	$('<div/>', {
		    	'class'	: 'wrapper',
		    	html	: htmlElements.join( '' )
		  	}).appendTo( '#list_elements' );

			//check if the element is in the view port. If yes, add a class for a smooth transition effect
			$('.list-element').inViewport(
	            function(){$(this).addClass("in-view");},
	            function(){$(this).removeClass("in-view");}
	        );
		  	
	    });

	    //get the JSON for the detail page based on the item id send with the link from the listing page
	    var item_id = getAllUrlParams().id;

	    //check if the id exist and if is not null
	    if(item_id && item_id != ''){

	    	$.getJSON(json_link,
			{	
			  	format: "json"
			}, function(data) {	

				//filter the json by id
				var single_item = data.items.filter(function(i) {
				  return i.id === item_id;
				});
			
		  		var title 	= single_item[0].snippet.title,
		  			date 	= single_item[0].contentDetails.videoPublishedAt;

		    	var content_body = "<div class='element_detail' data-id='" + single_item[0].id + "'><h1 class='title'>" + single_item[0].snippet.title + "</h1><span class='date'>Published on " + return_formated_date(date) + "</span><div class='video_wrapper'><iframe width='560' height='315' src='http://www.youtube.com/embed/" + single_item[0].snippet.resourceId.videoId + "' frameborder='0' allowfullscreen></iframe></div><p>" + single_item[0].snippet.description + "</p></div>";

		  		$('#list_detailed .wrapper').html(content_body);

		  		//call the function that make the youtube video responsive
		  		make_youtube_player_responsive();
	    	});
	    }

	});

})();

If you want to use this project, please clone it from https://nicolasflorth@bitbucket.org/nicolasflorth/youtube-list-json.git

Steps to use it:

  • run in command line (like git bash) "npm install";
  • after everything from package.json is installed, run in the command line "gulp"

Including a videos list with lazy load into your website is not hard once you know JS, AJAX and a bit of HTML and you follow the above steps.