Advertisement

Finishing the Responsive Timeline Portfolio Page

by

This Cyber Monday Tuts+ courses will be reduced to just $3 (usually $15). Don't miss out.

This post is part of a series called Design and Build a Responsive Timeline Portfolio Page.
Building the Responsive Timeline Portfolio Page
Final product image
What You'll Be Creating

Moving on from the previous tutorial, let's add some flourish to our build.

Loading Icon

We are going to delve into CSS3 Animations and create a simple spinning animation for our loading icon. We already have the markup we need on the page, so let's get straight into the CSS.

div.loading {
    color: darken($grey, 20%);
    position: absolute;
    width: 100px;
    bottom: 15px;
    left: 50%;
    margin-left: -50px;

    img {
        vertical-align: middle;

        &.rotate {
            -webkit-animation-name: rotate;
            -ms-animation-name: rotate;
            animation-name: rotate;
            -webkit-animation-duration: 1s;
            animation-duration: 1s;
            -webkit-animation-iteration-count: infinite;
            -ms-animation-iteration-count: infinite;
            animation-iteration-count: infinite;
            -webkit-animation-timing-function: linear;
            -ms-animation-timing-function: linear;
            animation-timing-function: linear;
        }


    }
}

Place this code right after the portfolio-item block in our Sass file. To start with, here we are just setting up some styles for the loading div itself. We are absolutely positioning it and making sure it is centred by using a margin-left of -50px, which is half of the element's width. This is to make up for the fact CSS positions elements from the top left corner.

The next section is much more interesting. As you may have seen in the HTML we assigned a class of rotate to the loading <img> tag which we will use as the hook to perform a CSS animation. The styles here are the set up for our animation. 

We are basically telling CSS what our animation function is called (we will create that in a moment), how long the animation should run, how many times it should run and the timing or easing function to use. For our project we want a nice smooth 360 degree rotation that never stops. The code above will achieve exactly that. 

Note: We have to include browser prefixed declarations so nobody misses out - and we could improve this by using a Sass mixin to do the heavy lifting for us.

Let's take a look in the browser.

OK, it's looking really good, but there's one thing missing. It isn't animating! Let's fix that now with the following code:

@-webkit-keyframes rotate {
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
        -webkit-transform-origin: center center;
        transform-origin: center center;
    }
}

@keyframes rotate {
    100% {
        -webkit-transform: rotate(360deg);
        -ms-transform: rotate(360deg);
        transform: rotate(360deg);
        -webkit-transform-origin: center center;
        -ms-transform-origin: center center;
        transform-origin: center center;
    }
}

So here is the rotate function. The way this works is by setting key points in the animation to change the element. You could set something to happen at 0%, 25%, 50% and so on. Our animation is very simple, so we just need a declaration at 100% which states the element must be rotated 360 degrees. This means our element will perform an entire spin and end up back where it started. Our earlier set up makes sure it will keep going indefinitely. The transform-origin property tells CSS where the centre point for the animation is. We could set it to spin with the top left corner as the centre (that might look interesting!) but we just want to use the exact centre of our element here. Save this file and take a look in the browser!

It should look similar to the above, but a lot smoother and faster.

That wraps up our Timeline Portfolio page, but there is one thing we can add to this for the purposes of this tutorial.

Loading Content

Now, in the real world this type of layout/theme would probably contain some kind of infinite scroll feature where more items appear as you scroll down the page, preventing you from having to click to go through pages.

We can simulate this behaviour here with a few lines of jQuery. Open up app.js and begin by adding the following, above where we wrote our menu title click handler.

var visibleHeight = $(document).height() - $(window).height();
var items;

First of all we need to declare two variables which we can use later on. The second one is purely a declaration and will be assigned a value later. The first one, visibleHeight, grabs the window height and subtracts that from the overall document height. This will leave us with the height of the area that is currently visible in the users' browser.

storeElements();

$(window).on('resize', function(e) {
    updateHeight();
});

$(window).on('scroll', function(e) {
    loadContent();
});

Next up, add the above snippet. You will see a few function calls to functions that don't exist yet, which is OK as we will create them next.

function storeElements() {
    items = $('.portfolio-item:lt(3)').clone();
    //Strip the first class from selection
    items.removeClass('first');
}

The storeElements function serves as a way to populate our items variable with some DOM elements. In our case we want to grab the first three portfolio-items. The use of jQuery's lt() allows us to selectively pick the elements we want. Once we have the selection we should clone() them, so we aren't using the actual elements but a copy instead. The last step is to remove the first class if it exists, as none of our new elements are the first in the list.

function updateHeight() {
    visibleHeight = $(document).height() - $(window).height();
}

Place this function above storeElements. This is probably the simplest function as it is only doing what we did on document ready. The reason I have used a function to do this is to keep it reusable.

Now we get to the function that is doing the work...

function loadContent() {

    if($(window).scrollTop() >= visibleHeight) {

        $(window).unbind('scroll');
        
        var loadingWrap = $('.loading-wrap');
        
        loadingWrap.fadeIn(function() {
            setTimeout(function() {
                loadingWrap.before(items);
                loadingWrap.hide(function() {
                    updateHeight();
                    storeElements();
                    $(window).on('scroll', function() { loadContent(); });
                });
            }, 500);
        });

    }
}

OK, step by step on what we are doing here:

  • Check if the scroll position is more than (scrolled past) or equal to (currently at) visibleHeight.
  • If it is, remove the scroll event handler from the window so we can process the content.
  • Cache loading-wrap for use later.
  • Fade loading-wrap in and once the fade completes...
  • ...set a small Timeout to simulate "loading" the content.
  • Attach our cloned items before the loading-wrap. This will slot in nicely between the loading icon and the current portfolio-items.
  • Hide loading-wrap and once hidden updateHeightstoreElements and re-attach the scroll event to the window with instructions to run this function again.

Phew! That seems like a lot, so run through it again if you need to. One thing you may notice, if you are eagled-eyed, is that we are fading in the loading-wrap which is actually already visible in our HTML. Fix that by adding an inline style to that element...

<div class="portfolio-item group loading-wrap" style="display:none;">

So, if we now save our files and view our work in the browser you should see some more content "load" in once you scroll to the bottom of the page. Oh wait, there is one other thing we need to add...

$('.menus h3').on('click', function(e) {
    $(this).next('ul').toggleClass('open');
    updateHeight();
    e.preventDefault(); return false;
});

Inside the h3 click handler we created right at the start we must add in a call toupdateHeight(), this way when we click to open a menu the variables are updated to reflect the change in height of the document. Without this, if you open a menu on mobile the "loading" functionality would be broken as the visibleHeight variable would be incorrect.

Conclusion

We have reached the end! We could probably tidy up the JavaScript we have just written and, in a real world situation it would probably be changed to an AJAX call or something.

I hope you have learnt some different techniques following along with this tutorial. It was fun to build and I'm happy to share with you the process to bring this very nice design to life. I'm sure there are plenty of ways to improve this, so if you have any suggestions please do let me know in the comments!

Advertisement