Infinite Scroll, Card Design, Responsive Tutorial
Card design and infinite scrolling are two relatively new trends in website design that are wonderful for displaying large amounts of information. Card design allows you to show your visitors previews of content, or small content in easily understandable, small bits. Infinite-scrolling allows your visitors to see all of your content options without having to click links, reload their screen, or download large amounts of data. It combines the reduced load time provided by pagination with the usability of providing your content in a single page.
When combined together, card design and infinite scroll allow you to show your content clearly, cleanly, and in a easily accessible way to your visitors.
Contents
This tutorial is divided into 7 sections with a conclusion at the end.
- The Card Design: forming the basic HTML and CSS files that defining the card design.
- Efficient, Dynamic Alignment using Masonry: aligning the cards using Masonry.
- Infinite Scrolling: Adding Cards: creating infinite scrolling by dynamically adding new cards when we reach the end of the page.
- Loading Data using AJAX: loading our demo card data using AJAX from our server.
- Creating Cards with PHP: using a PHP to create our cards with our demo data.
- Loading Card Data with PHP and MySQL: using our PHP script to load data from our MySQL table.
- At the End of the Results: stopping when we reach the end of our results.
- Conclusion
Click here to view the final infinite scroll, card design demo. Download the final source code for the demo
The Card Design
To begin, we will start with a blank HTML and CSS webpage. We will have our HTML document with the basic header, and body. We will also include our CSS file in the header.
<!DOCTYPE html>*<html>*<head>*<title>Card Design Template</title>*<link rel="stylesheet" type="text/css" href="cardDesign.css" />*</head>*<body>*</body>*</html>
For the basic CSS, we will set some basic settings for the body tag, clearing any default padding, setting the base font preferences, and setting a light gray background color.
body*{*width: 100%;*padding: 0;*margin: 0;*font-family: sans-serif;*background: #E8E8E8;*}
Within the body section, we will add a div element that will contain our cards.
<body>*<div id="cardContainer">*</div></body>
We will also define a few CSS settings, making the element slightly thinner than the full page width, and centering the text within the element.
#cardContainer*{*position: relative;*width: 95%;*margin: 0 2.5% 0 2.5%;*text-align: center;*}
Next, we will create our basic card design. Your card can be basically anything that you want: images, text, even videos or elements containing other effects like parallax scrolling. The only requirements are that it fit within a div element, and that the div element has the correct dimensions.
For our cards, we will use a simple design. We will create a div element with class “card”. Inside, we will include a top image, a title for the card, and a paragraph.
<body><div id="cardContainer">*<div class="card">*<div class="card_top"></div>*<h2>Card #1</h2>*<p>This is an example card. It is the first card in the deck.</p>*</div></div></body>
For the CSS of the card, we will set the position to relative, the minimum width to 200 pixels, the background to a 70% transparent white, create a light shadow around the card, and add some slight rounding to the corners. For our example, we will be using a maximum of 4 columns. Therefore, we will create a 1.5% margin on both sides (3% in total), and set the default width to 22% (100% / 4 columns – the card’s margin). We will also add a vertical margin for the cards of 0.5em above and 1.5em below.
We will also add some basic styling to our paragraph within the card. We will set the margin to 1em above and below, and 5 pixels to the left and right. We will set the font size to 0.85 ems and set the text alignment to “left”.
body{width: 100%;padding: 0;margin: 0;font-family: sans-serif;background: #E8E8E8;}#cardContainer{position: relative;width: 95%;margin: 0 2.5% 0 2.5%;text-align: center;}*.card*{*position: relative;*width: 22%;*min-width: 200px;*margin: 0.5em 1.5% 1.5em 1.5%;*background: rgba(255, 255, 255, 0.7);*box-shadow: 0px 0px 5px rgba(64, 64, 64, 0.4);*border-radius: 5px;*}*.card p*{*margin: 1em 10px;*font-size: 0.85em;*text-align: left;*}*.card_top*{*width: 100%;*height: 200px;*background: #F00;*}
Let’s also add some additional cards for demonstration purposes, giving them different heights for variety. To create additional cards, simply use the “card” class that we just created. You can add different titles and paragraphs, or leave parts of the cards out entirely if you desire.
<!DOCTYPE html><html><head><title>Card Design Template</title><link rel="stylesheet" type="text/css" href="cardDesign.css" /><script src="masonry_min.js"></script></head><body><body><div id="cardContainer"><div class="card"><div class="card_top"></div><h2>Card #1</h2><p>This is an example card. It is the first card in the deck.</p></div>*<div class="card" style="height: 300px;">*<h2>Card #2</h2>*</div>*<div class="card" style="height: 200px;">*<h2>Card #3</h2>*</div>*<div class="card" style="height: 200px;">*<h2>Card #4</h2>*</div>*<div class="card">*<div class="card_top"></div>*<h2>Card #5</h2>*</div>*<div class="card" style="height: 400px;">*<h2>Card #6</h2>*</div>*<div class="card" style="height: 200px;">*<h2>Card #7</h2>*</div>*<div class="card" style="height: 350px;">*<h2>Card #8</h2>*</div>*<div class="card" style="height: 200px;">*<h2>Card #9</h2>*</div>*<div class="card" style="height: 250px;">*<h2>Card #10</h2>*</div>*<div class="card" style="height: 200px;">*<h2>Card #11</h2>*</div>*<div class="card" style="height: 300px;">*<h2>Card #12</h2>*</div>*<div class="card" style="height: 250px;">*<h2>Card #13</h2>*</div>*</div></body></html>
These will appear in one column vertically on the page. Our next step will be to align them.
Efficient, Dynamic Alignment using Masonry
Now that we have our basic card design, we will use the Masonry JavaScript script to align our cards. First, you will need to download the minified version of masonry, save it in your working folder (we renamed it “masonry_min.js”), and add a script tag to the HTML header telling the page to load the file.
<!DOCTYPE html><html><head><title>Card Design Template</title><link rel="stylesheet" type="text/css" href="cardDesign.css" />*<script src="masonry_min.js"></script></head>
Next, we need to create a “sizer” for the columns. This is a simple div element that will not be seen, located directly after we start our card container. We can define the width of our columns using this element.
<body><div id="cardContainer">*<div class="grid-sizer"></div>
In the CSS, we can now add an entry for the sizer. We will create an entry for our grid sizer as a child of the main card container, and set the width to our desired column width. Just as before, we are going to use a maximum of 4 columns, so we need a column width of 25%. This element will not be seen, so we don’t need any height or other style settings.
#cardContainer{position: relative;width: 95%;margin: 0 2.5% 0 2.5%;text-align: center;}*#cardContainer .grid-sizer*{*width: 25%;*}
Next, we need to add some JavaScript. First add the script tags to the end of the page body. Within these tags, we need to create a variable to recognize our container that holds all of our tags. As a general rule, masonry prefers using selectors, so we will use a query selector to get our card container element.
</div>*<script>
*ar container = document.querySelector('#cardContainer');
Next we will define the masonry object. To do this, we call the Masonry function, specifying our container variable pointing to our card container, and a set of options. We will set the width of our columns to the grid sizer element that we just created, and set the item selector to our “card” class (don’t forget the period before the class name since Masonry uses selectors.) This tells Masonry to only use elements with our card class, not for example, our grid sizer element that is within our container, but that we don’t want included in the calculations.
<script>
var container = document.querySelector('#cardContainer');*var msnry = new Masonry(container, {*"columnWidth": ".grid-sizer",*"itemSelector": ".card"*});
When you load the page, your cards should now be aligned and organized in 4 columns. It is worth noting that Masonry arranges the cards in order, but places the in the column by whichever is highest. Therefore, your cards will not appear from left to right, as would be expected if you were using the inline-block display setting in CSS3.
Infinite Scrolling: Adding Cards
To have our page “infinite-scroll”, adding cards as we reach the end, we need to be able to add more cards. We will first need to know when we need more cards. To do this, we need to create a JavaScript function to monitor when visitors scroll down the page. This is done using the window.onscroll monitor in JavaScript, and setting it to a custom function that we will define immediately.
<script>
var container = document.querySelector('#cardContainer');var msnry = new Masonry(container, {"columnWidth": ".grid-sizer","itemSelector": ".card"});*window.onscroll = function()*{*};
We need to know the where the end of the columns is; when we reach there, we need to have more cards added. To do this we will need to sum the number of pixels above our card container and the height of the card container. These values are given by the offsetTop and offsetHeight properties of our container variable.
<script>
var container = document.querySelector('#cardContainer');var msnry = new Masonry(container, {"columnWidth": ".grid-sizer","itemSelector": ".card"});*var heightEnd = container.offsetHeight + container.offsetTop;window.onscroll = function(){*heightEnd = container.offsetHeight + container.offsetTop;};
We also need to know how far our visitors have scrolled down the page. The pageYOffset property of the window system variable will give us how far down the page the visitor has scrolled, as measured from the top of the window. However, we need to know the height from the bottom of the window. To get this value, we will need to also know the window height. This is given by the innerHeight property of the window system variable
<script>
var container = document.querySelector('#cardContainer');var msnry = new Masonry(container, {"columnWidth": ".grid-sizer","itemSelector": ".card"});var heightEnd = container.offsetHeight + container.offsetTop;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;*yOffset = window.pageYOffset;*windowHeight = window.innerHeight;};
We also should create a variable to act as a buffer. This will define how far above the end we should start loading the cards. This way, when your visitors reach the end, they won’t have to wait as long to get the new cards from your server. In this tutorial, we will be using a buffer of 200 pixels.
<script>
var container = document.querySelector('#cardContainer');var msnry = new Masonry(container, {"columnWidth": ".grid-sizer","itemSelector": ".card"});var heightEnd = container.offsetHeight + container.offsetTop;*var loadBuffer = 200;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;};
Next, we will create a condition to test if we have reached the end and need to load more cards. We need to test to see if our visitors bottom location (the amount the visitors have scrolled down, plus the height of their window) is greater than or equal to the container end height, minus our load buffer. If this is true, we have reached the end and we need to load more cards.
<script>
window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;*if(yOffset + windowHeight >= heightEnd - loadBuffer)*{*}};
Masonry adds elements that are specified in an array. So, we will create an array variable to store our new cards that we want added.
<script>
window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){*var cardsToAdd = [];}};
Next, we will create a fragment. A fragment, as the name suggests, is basically a part of the HTML that won’t be immediately displayed on the page. To initialize it, we use the createDocumentFragment() function.
<script>
window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){var cardsToAdd = [];*var fragment = document.createDocumentFragment();}};
At this step in the demo, we will be adding 15 temporary cards each time we reach the end of the page. This will allow us to test our infinite scrolling.
<script>
window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){var cardsToAdd = [];var fragment = document.createDocumentFragment();*for(var i = 0; i < 15; i++ )*{*}}};
We need to create a function that will create our new sample cards. To do this, we will create a function called addCard. This won’t add the card directly to the page, but instead return a variable reference back to us. We will store this in a variable called newCard.
As a parameter to this function, we will send a variable that will count the number of cards that we have added. We also need to add a variable, outside our window.onscroll event function, that will count how many cards we have added.
*unction addCard(id)*{*}var container = document.querySelector('#cardContainer');var msnry = new Masonry(container, {"columnWidth": ".grid-sizer","itemSelector": ".card"});var heightEnd = container.offsetHeight + container.offsetTop;var loadBuffer = 200;*var cardsAddedCount = 0;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){var cardsToAdd = [];var fragment = document.createDocumentFragment();for(var i = 0; i < 15; i++ ){*var newCard = addCard(cardsAddedCount);}}};
Within this new function, we will first create a div element. We will define the class name to our “card” class that we have been using.
function addCard(id){*var elem = document.createElement('div');*elem.className = "card";}
Next we will set the inner HTML of our new card to some test content. We are adding a top section, a title specifying the number of cards that have been added, and some basic lorem ipsum text. Finally, we need to return our variable referencing the new card so that we can work with it.
function addCard(id){var elem = document.createElement('div');elem.className = "card";*elem.innerHTML = "<div class=\"card_top\"></div><h2>Card Added #"+(id+1)+"</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>";*return elem;}
Back in our main function, we need to add our newly created card to our fragment. We do this using the appendChild() function. We also need to push our new card to our array of cards that we want to add to the page. We do this using the push() function for our cardsToAdd array. Finally, we will increment our counter variable that will keep track of how many cards we have added.
<script>
function addCard(id){var elem = document.createElement('div');elem.className = "card";elem.innerHTML = "<div class=\"card_top\"></div><h2>Card Added #"+(id+1)+"</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>";return elem;}var heightEnd = container.offsetHeight + container.offsetTop;var loadBuffer = 200;var cardsAddedCount = 0;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){var cardsToAdd = [];var fragment = document.createDocumentFragment();for(var i = 0; i < 15; i++ ){var newCard = addCard(cardsAddedCount);*fragment.appendChild(newCard);*cardsToAdd.push(newCard);*cardsAddedCount++;}}};
We now need to append our fragment, containing our new cards, to the end of our cards container. We do this using the appendChild() function for our container, passing the fragment as a parameter.
<script>
function addCard(id){var elem = document.createElement('div');elem.className = "card";elem.innerHTML = "<div class=\"card_top\"></div><h2>Card Added #"+(id+1)+"</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>";return elem;}var heightEnd = container.offsetHeight + container.offsetTop;var loadBuffer = 200;var cardsAddedCount = 0;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){var cardsToAdd = [];var fragment = document.createDocumentFragment();for(var i = 0; i < 15; i++ ){var newCard = addCard(cardsAddedCount);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}*container.appendChild(fragment);}};
We next need to have Masonry append the cards to the page. This will have Masonry calculate the positioning of our new cards, and add them in the optimal location. We do this by calling the appended() function to our masonry variable (the one where we stored the result of calling the Masonry() function with our container and options).
<script>
function addCard(id){var elem = document.createElement('div');elem.className = "card";elem.innerHTML = "<div class=\"card_top\"></div><h2>Card Added #"+(id+1)+"</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>";return elem;}var heightEnd = container.offsetHeight + container.offsetTop;var loadBuffer = 200;var cardsAddedCount = 0;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){var cardsToAdd = [];var fragment = document.createDocumentFragment();for(var i = 0; i < 15; i++ ){var newCard = addCard(cardsAddedCount);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}container.appendChild(fragment);*msnry.appended(cardsToAdd);}};
Finally, we need to recalculate where the end of our page is. We do this using the same summation as we did earlier, simply copying and pasting the line that we wrote before.
<script>
function addCard(id){var elem = document.createElement('div');elem.className = "card";elem.innerHTML = "<div class=\"card_top\"></div><h2>Card Added #"+(id+1)+"</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>";return elem;}var heightEnd = container.offsetHeight + container.offsetTop;var loadBuffer = 200;var cardsAddedCount = 0;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){var cardsToAdd = [];var fragment = document.createDocumentFragment();for(var i = 0; i < 15; i++ ){var newCard = addCard(cardsAddedCount);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}container.appendChild(fragment);msnry.appended(cardsToAdd);*heightEnd = container.offsetHeight + container.offsetTop;}};
At this point, our demo page should have our base cards. When you scroll down, the page should automatically add more cards. The cards should have a title telling you how many cards have been added. You should be able to scroll indefinitely; cards should simply be added to the end of the page.
Loading Data using AJAX
At this point, in order for AJAX to work, you will need to use a web server, instead of just using a local file on your computer. A local test web server with PHP will work very well, or you can use a hosing server.
Next, instead of adding our test data 15 times as generated in the page using Javascript, we will load the data using AJAX. To do this, we need to have a file that will store the contents of the cards. Temporarily, we will simply duplicate our test data that we generated using our addCard() function, storing our code in a separate file (removing the quotes, character escaping, and the JavaScript that displays the number of cards we have added previously).
Since HTML is not affected by new lines, we can simply place our HTML code for our new card contents in single, individual lines. We can simply duplicate the text 15 times, and save the file as “cardData.html”. Click here to download the test data file.
<div class="card_top"></div><h2>Card Added</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>(Repeated 15x)
If you are using your own web server for this demo, feel free to upload this file to your server. Otherwise, the file will be located at http:
Next, we need to change our “id” parameter to the addCard function to a variable named “contents”. This will specify the contents of our new card. We will also set the innerHTML to our new contents, instead of using static text.
*unction addCard(contents){var newCard = document.createElement('div');newCard.className = "card";*newCard.innerHTML = contents;return newCard;}
Next, we will create some AJAX code to load our card data. This will need to run between our line creating the fragment and our loop adding the cards. We will use a standard AJAX request, creating a request variable, opening a GET request for our “cardData.html” location, and setting asynchronous requests to true.
var fragment = document.createDocumentFragment();*var xmlhttp;*if(window.XMLHttpRequest){xmlhttp = new XMLHttpRequest();}*else{xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}*xmlhttp.open("GET", "cardData.html", false);*xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
We then create our callback function for when we get a response back from your server (or ours), storing our response in a variable named “data”. We then send the request.
var fragment = document.createDocumentFragment();var xmlhttp;if(window.XMLHttpRequest){xmlhttp = new XMLHttpRequest();}else{xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}xmlhttp.open("GET", "cardData.html", false);xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");*xmlhttp.onreadystatechange=function()*{*if(xmlhttp.readyState==4 && xmlhttp.status==200)*{*var response = xmlhttp.responseText;*}*};*xmlhttp.send();
Next, we need to split our response back into the data for the individual cards. To do this, we will separate the response between the newlines “\n”; the split() function will accomplish this.
xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;*var cardsArray = response.split("\n");}};xmlhttp.send();
We now need to count how many cards were given to us and that we need to add. To calculate this, we can simply count the number of elements in our cardsArray variable, using the length property.
xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;var cardsArray = response.split("\n");*var numberOfCards = cardsArray.length;}};xmlhttp.send();
We need to avoid the last card if it is blank. To do this, we simply check the last element, see if it is blank, and if it is, we decrease the number of cards by 1.
xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;var cardsArray = response.split("\n");var numberOfCards = cardsArray.length;*if(cardsArray[numberOfCards - 1] == "") numberOfCards--;}};xmlhttp.send();
Next, we need to cut and paste our card adding loop from before in our function.
We need to change the 15 value to the numberOfCards variable. We also need to change the parameter to the addCard() function to our current card data, stored in our cards array at the current index: cardsArray[i].
xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;var cardsArray = response.split("\n");var numberOfCards = cardsArray.length;if(cardsArray[numberOfCards - 1] == "") numberOfCards--;*for(var i = 0; i < numberOfCards; i++ ){*var newCard = addCard(cardsArray[i]);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}}};xmlhttp.send();
Finally, cut and paste the code updating the container and Masonry to directly below our loop.
xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;var cardsArray = response.split("\n");var numberOfCards = cardsArray.length;if(cardsArray[numberOfCards - 1] == "") numberOfCards--;for(var i = 0; i < numberOfCards; i++ ){var newCard = addCard(cardsArray[i]);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}*container.appendChild(fragment);*msnry.appended(cardsToAdd);*heightEnd = container.offsetHeight + container.offsetTop;}};xmlhttp.send();
At this point, you should be able to scroll down and new cards should appear, loaded from our HTML file via the AJAX call.
We also need to make sure that we wait until the previous request has been made before we make a new one. To do this, we will have a simple boolean variable, “doneLoading”.
We will initially set it to true, since we aren’t loading anything. Next, within our condition for loading new cards, we will test if we are still loading new cards (when doneLoading is false); if so, we will simply return out of the function. If not, we need to load new cards, and will indicate that by setting “doneLoading” to false.
Finally, when we are finished loading our new cards, we will set the doneLoading variable to true, indicating that we can now load new cards.
<script>
function addCard(contents){var newCard = document.createElement('div');newCard.className = "card";newCard.innerHTML = contents;return newCard;}var heightEnd = container.offsetHeight + container.offsetTop;var loadBuffer = 200;var cardsAddedCount = 0;*var doneLoding = true;window.onscroll = function(){heightEnd = container.offsetHeight + container.offsetTop;yOffset = window.pageYOffset;windowHeight = window.innerHeight;if(yOffset + windowHeight >= heightEnd - loadBuffer){*if(doneLoading == false) return;*doneLoading = false;var cardsToAdd = [];var fragment = document.createDocumentFragment();xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;var cardsArray = response.split("\n");var numberOfCards = cardsArray.length;if(cardsArray[numberOfCards - 1] == "") numberOfCards--;for(var i = 0; i < numberOfCards; i++ ){var newCard = addCard(cardsArray[i]);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}container.appendChild(fragment);msnry.appended(cardsToAdd);heightEnd = container.offsetHeight + container.offsetTop;*doneLoading = true;}};xmlhttp.send();}};
Click here to download the files for the tutorial so far.
Creating Cards with PHP
We now need to create new cards using PHP. This will eventually allow us to load new cards with real data, instead of our test data. Just like before, we can provide our cards on individual lines in our response. Within those however, we can include any HTML that we want displayed in our cards, including individual text and images.
We will first change our AJAX request to our PHP file instead of the static HTML file. We will name our new file “cardData.php” instead of “cardData.html”. We should also send how many cards we have already added to our script. This will tell our PHP script which cards it needs to give back and which have already been added. To do this, we will add a GET parameter to the end of the URL, setting “n” equal to our cardsAddedCount variable: cardData.php?n=[cardsAddedCount].
var fragment = document.createDocumentFragment();var xmlhttp;if(window.XMLHttpRequest){xmlhttp = new XMLHttpRequest();}else{xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}*xmlhttp.open("GET", "cardData.php?n="+cardsAddedCount, false);xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;var cardsArray = response.split("\n");var numberOfCards = cardsArray.length;if(cardsArray[numberOfCards - 1] == "") numberOfCards--;for(var i = 0; i < numberOfCards; i++ ){var newCard = addCard(cardsArray[i]);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}container.appendChild(fragment);msnry.appended(cardsToAdd);heightEnd = container.offsetHeight + container.offsetTop;}};xmlhttp.send();
Next, we need to build our PHP script. This will be heavily dependent upon your own environment and what you wish to display on your website. We will eventually be using our PHP script to obtain cards from a MySQL table, and return them to the website in order to be displayed on the page. However, we will first test to make sure that our system is working properly, using the same test data as before.
The first step is to add our PHP tag initializers. Next, we need to connect to our MySQL database. This will completely depend upon your server, so use your own settings.
<?PHP *$mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");?>
Next, we need to retrieve the number of cards that we have already added. We sent this to the PHP script via a GET paramater (named “n”) in our AJAX request. To obtain the value from the URL, we simply use the $_GET array system variable, selecting the element with index “n”. We also need to escape our strings to prevent SQL injection attacks.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");*$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);?>
To make sure that our system is working, we will now create the test data that we had before, with the title specifying the number of the card. To do this, we simply create a loop with 15 increments, just as before. We now copy and paste a line of HTML from our static file, enclosing it in quotes and storing it in a variable named “$card”. Don’t forget to escape the quotes within the echo statement.
Next we need to add an expression within the title tag to show which card number it is. This number is simply the sum of the number of cards we have previously added, the array index, and 1 so that we start from 1 and not 0.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);*for($i = 0; $i < 15; $i++)*{*$card = "<div class=\"card_top\"></div><h2>Card Added ". ($cardsAdded + $i + 1). "</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>";*}?>
Finally, we remove any newlines from the code (just to make sure that our code works properly and that we can separate the cards properly with our JavaScript code) and echo the result with a newline at the end.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);for($i = 0; $i < 15; $i++){$card = "<div class=\"card_top\"></div><h2>Card Added ". ($cardsAdded + $i + 1). "</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel cursus arcu, id condimentum quam. Fusce dignissim tortor est, sit amet finibus lacus sodales quis. Maecenas elementum velit vitae convallis malesuada. Nam elementum purus a urna tempor molestie. Vivamus quam nibh, ultrices at dapibus sed, maximus ac metus.Sed arcu ex, ullamcorper et ipsum non, aliquam vestibulum lorem.</p>";*echo str_replace("\n", "", $card). "\n";}?>
If everything works properly, you should now be able to reload the page. You should be able to scroll down and the cards should load just like before, with the card number specified in the title. This time however, we are calling the cards dynamically from the server, instead of dynamically generating them with JavaScript or loading them statically with HTML.
Loading Card Data with PHP and MySQL
Finally, we need to load our card data from our MySQL table. To do this, we will simply obtain our card data, form our card HTML, and store it in our $card PHP variable. When we call our script, it will then obtain the data and send it back to the webpage, just as it did with our test data. We can remove our for loop that generates our test data now.
First, we need to create a query to obtain the data from our table for the cards. We will select the title, description, URL, and imageURL from our table, without any constraints, ordering by id in descending order. We will also select rows starting at our last row (contained in our $cardsAdded variable that holds the number we sent via a GET parameter), and we will select 15 rows.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);*$selectQuery = "SELECT title, description, url, imageURL FROM [yourTable] WHERE 1 ORDER BY id DESC LIMIT $cardsAdded, 15";?>
We will then perform the query. If we get a result back (if the result isn’t NULL), we will declare a variable to store our row from the result. Next, we will loop through all of the rows that our query gave us.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);$selectQuery = "SELECT title, description, url, imageURL FROM [yourTable] WHERE 1 ORDER BY id DESC LIMIT $cardsAdded, 15";*if(($selectRes = mysqli_query($mysqlConn, $selectQuery)) != NULL)*{*}?>
If the row exists (it isn’t NULL), we should save our data from our MySQL table into PHP variables for ease of use. We can simply create variables, each for the title, description, url, and imageURL, and save the values in the row array respective to each field.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);$selectQuery = "SELECT title, description, url, imageURL FROM [yourTable] WHERE 1 ORDER BY id DESC LIMIT $cardsAdded, 15";if(($selectRes = mysqli_query($mysqlConn, $selectQuery)) != NULL){*$selectRow;*while(($selectRow = mysqli_fetch_row($selectRes)) != NULL)*{*$title = $selectRow[0];*$description = $selectRow[1];*$url = $selectRow[2];*$imageURL = $selectRow[3];*}}?>
Next, we will form our card variable similarly to we did before. This time however, we will be using the data we obtained from the query instead of testing data. If we have an image for our card, we will add it, encapsulating it as a link. We will also create a link for our header pointing to the URL field, we will set the title to our “title” variable, and we will set our paragraph tag equal to our “description” variable.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);$selectQuery = "SELECT title, description, url, imageURL FROM [yourTable] WHERE 1 ORDER BY id DESC LIMIT $cardsAdded, 15";if(($selectRes = mysqli_query($mysqlConn, $selectQuery)) != NULL){$selectRow;while(($selectRow = mysqli_fetch_row($selectRes)) != NULL){$title = $selectRow[0];$description = $selectRow[1];$url = $selectRow[2];$imageURL = $selectRow[3];*$card = "";*if($imageURL != "")*{*$card .= "<a href=\"$url\"><div class=\"card_top\" style=\"background: url('$imageURL') top left no-repeat;\"></div></a>";*}*$card .= "<a href=\"$url\"><h2>$title</h2></a><p>$description</p>";}}?>
We will then use the same newline removal expression, appending a newline to the end, and then echo the result to our page.
<?PHP $mysqlConn = mysqli_connect("yourServer", "yourUsername", "yourPassword", "yourDatabase");$cardsAdded = mysqli_real_escape_string($mysqlConn, $_GET["n"]);$selectQuery = "SELECT title, description, url, imageURL FROM [yourTable] WHERE 1 ORDER BY id DESC LIMIT $cardsAdded, 15";if(($selectRes = mysqli_query($mysqlConn, $selectQuery)) != NULL){$selectRow;while(($selectRow = mysqli_fetch_row($selectRes)) != NULL){$title = $selectRow[0];$description = $selectRow[1];$url = $selectRow[2];$imageURL = $selectRow[3];$card = "";if($imageURL != ""){$card .= "<a href=\"$url\"><div class=\"card_top\" style=\"background: url('$imageURL') top left no-repeat;\"></div></a>";}$card .= "<a href=\"$url\"><h2>$title</h2></a><p>$description</p>";*echo str_replace("\n", "", $card). "\n";}}?>
Now, when we scroll down the page, new results should appear, obtained from our MySQL table.
At the End of the Results
When we reach the end of the results, we need to stop requesting more results. The easiest way to determine this is to test if our PHP script gave us no new cards, determined if the numberOfCards variable is less than or equal to 1. If we are at the end of our results, we need to set a boolean variable, “atEnd”, indicating that we are at the end to “true”, and change our original scroll condition to stop testing our scroll position. We can do this by simply testing if “atEnd” is false. If it isn’t, we will simply skip the function.
<script>
function addCard(contents){var newCard = document.createElement('div');newCard.className = "card";newCard.innerHTML = contents;return newCard;}var container = document.querySelector('#cardContainer');var msnry = new Masonry(container, {"columnWidth": ".grid-sizer","itemSelector": ".card"});var heightEnd = container.offsetHeight + container.offsetTop;var loadBuffer = 200;var cardsAddedCount = 0;var doneLoading = true;*var atEnd = false;window.onscroll = function(){heightEnd = container.offsetTop + container.offsetHeight;yOffset = window.pageYOffset;windowHeight = window.innerHeight;*if(atEnd == false && yOffset + windowHeight >= heightEnd - loadBuffer){if(doneLoading == false) return;doneLoading = false;var cardsToAdd = [];var fragment = document.createDocumentFragment();var xmlhttp;if(window.XMLHttpRequest){xmlhttp = new XMLHttpRequest();}else{xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");}xmlhttp.open("GET", "http://192.168.1.205/cardDesign/cardData.php?n="+cardsAddedCount, false);xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");xmlhttp.onreadystatechange=function(){if(xmlhttp.readyState==4 && xmlhttp.status==200){var response = xmlhttp.responseText;var cardsArray = response.split("\n");var numberOfCards = cardsArray.length;*if(numberOfCards <= 1)*{*atEnd = true;*return;*}if(cardsArray[numberOfCards - 1] == "") numberOfCards--;for(var i = 0; i < numberOfCards; i++ ){var newCard = addCard(cardsArray[i]);fragment.appendChild(newCard);cardsToAdd.push(newCard);cardsAddedCount++;}container.appendChild(fragment);msnry.appended(cardsToAdd);heightEnd = container.offsetHeight + container.offsetTop;doneLoading = true;}};xmlhttp.send();}};
View the final demo | Click here to download the completed files for this tutorial.
Conclusion
So at this point you should have a webpage that dynamically loads content from your MySQL table, and shows it in an infinite-scrolling, card layout design.
For your own design, you will probably want to change the testing tiles at the top of the page to your own content. To prevent duplication though, you will want to add that number of tiles to your $cardsAdded value in your PHP script, within the MySQL query. This will allow you to skip the tiles that you have already added.