AngularJS, Skeleton Loader & Endless Scrolling
Revisiting a CSS Pre-load Preview Technique

I came across a 2015 post on StackOverflow today, and realized that I like this “placeholder grid” or “skeleton” style loading animation that was studied in an article by George Philips. This placeholder animation style will look familiar if you’ve been on Facebook or LinkedIn. The same placeholder style animation is even used here on Medium!

In an effort to implement something similar, I came up with a simple approach. This method uses dynamically populated Bootstrap 4 cards, and a CSS animation delay. Here’s how it works.
Key Components
__ Dynamic data loaded via AJAX (AngularJs HTTP)
__ Endless (infinite) scrolling__ more cards are displayed on scroll
__ Server-side and client-side paging (scroll-able groups)
__ Animations using Animate.css
__ Placeholder preview grid display before card content is populated
Implementation
Get Some Data
The data comes from the College Scorecard API, loaded using an AngularJs $http.get()
request…
var apiUrl = "https://api.data.gov/ed/collegescorecard/v1/schools.json?";
$scope.getData = function(startAt,count){
$scope.items = $scope.items||[]; $http.get(apiUrl+"&_page="+startAt+"&_per_page="+count+"")
.then(function(resp) {
var data = resp.data;
/* populate items */
$scope.items = $scope.items.concat(data.results);
$scope.lastIndex = $scope.items.length;
$scope.loading = false;
}, function(data, status) {
console.log("get data error!");
});
};
Populate the Cards
Once the data fills $scope.items
, the cards are populated using the Angular ng-repeat
directive…
<section class="row">
<div class="col-md-6 col-lg-3 py-3"
ng-repeat="item in items" ng-show="$index<pageIndex">
<div class="card bg-light animated fadeIn">
<div class="card-body bg-white animated fadeIn">
.. card content
</div>
</div>
</div>
</section>

Animate It!
As you can see, both the card
and the inner card-body
have the animated fadeIn
classes which runs the Animate.css animation when each card is displayed. You can also try other animation effects like flash
, pulse
, etc..
Here’s how the placeholder preview animation trick works. Notice that card
has a light gray background color bg-light
, and the card-body
has a white background color bg-white
. We just need a little CSS so that the animation on card-body
is delayed slightly 1.3s
.
As a result, the light gray card
appears first, and then the white card-body
fades in 1.3s
after, covering the light gray background of the card
…
.card-body {
-webkit-animation-delay:1.3s
}
Again, you can set the animation-delay
to whatever you’d like. Increase the delay time for a longer more dramatic the placeholder preview effect.

Keep it Scrolling
The endless page functionality works using an Angular directive I created called when-visible
. It it used to “watch” the current scroll position.
myApp.directive('whenVisible', function() {
/* this directive indicates when an element
is scrolled into view, and adds the `animated`
class to the element which triggers the animate.css
animations
*/
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + ($(window).outerHeight());
var elemTop = elem.offset().top;
var elemBottom = elemTop + elem.innerHeight();
return (elemTop <= docViewBottom) && (elemTop > docViewTop);
}
return function(scope, elm, attr) {
var raw = elm[0];
$(window).scroll(function(){
if (isScrolledIntoView(elm)) {
elm.addClass("animated");
if (!scope.busy){
setTimeout(function(){
scope.$apply(attr.whenVisible);
},500);
}
}
else {
elm.removeClass("animated");
}
return false;
});
};
});
The when-visible
trigger element is placed below the cards container…
<div when-visible="showMore(itemsPerPage)"
ng-show="!needToLoadMore">
</div>
When the when-visible
div is scrolled into view, the showMore()
function in the controller is executed. The showMore()
function displays the next group of cards…
$scope.showMore = function(howMany){
if (!$scope.busy){
$scope.pageIndex = $scope.pageIndex + howMany;
$scope.currentPage =
Math.floor($scope.pageIndex /$scope.itemsPerPage);
setTimeout(function(){
$scope.busy = false;
if ($scope.pageIndex >= $scope.lastIndex){
$scope.needToLoadMore = true;
}
else {
$scope.needToLoadMore = false;
}
},300);
$scope.busy = true;
}
};
Each request to the College Scorecard API loads 60 items. Each request is a single “page” of API data. However, each “page” of API data is shown in 5 groups of 12 as you scroll. These are like the pages within the page that are loaded as you scroll. Once all of the 5 card groups are shown, we’re out of API data. So, the “Load More” button is displayed to prompt another API request which gets the next 60 items. You can tweak the variables to however you’d like.
$scope.itemsPerPage = 12; // infinite scroll page(group) size
$scope.itemsFromServer = 60; // how many to get from API
$scope.pageIndex = 12; // items to show initially
Putting It All Together
Finally, here is the complete source and demo for this entire placeholder animation project. Enjoy!
Learn more about Bootstrap 4, and explore these helpful examples.