Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing fluid JS tile interface

I'm building a photography website, and I want to create a nice "tiled" interface, which will look similar to the interface on new version MSN Money Now (note - the new version of the website can be viewed only on Windows 8 PCs) - http://t.money.msn.com/now/. I tried to implement this in Javascript.

Here is a sample page with prefilled data: http://photoachiever.azurewebsites.net/en

I created Tile groups - each 2 units high, 2 units wide, which can contain either one big square tile, two wide tiles or four small square tiles. Now, because I want the site to be responsive, I wanted to calculate on the fly in Javascript the optimal unit size, so that always 100 % of the space are filled and for wider screens are for example more columns visible and so on. It works the same way on MSN Money website, but there are two important differences:

1) When my images load the first time, I just see them in their highest resultion up until the point where all images are loaded and the JS is executed. The MSN Money web just displays a green area and the images later appear, already resized appropriately. 2) When I resize the window, it is far from fluid and the caluclations and mainly image resizing are very significantly visible. On MSN Money however the resizing is very smooth and even the images seem to just resize without a glitch. Also - they managed to make the fonts resize fluidly.

Could you please explain me, how the MSN Money website achieved these results? I have seen a few similare questions here on Stack Overflow, but they never required the equal width and height of individual tiles, which I really need for my design.

Bonus question: Could you please add some explanations of how to achieve responsive animated reflow of divs? Example found on http://www.brainyquote.com/ - when you change Window size, it reflows all quotes in an animated manner.

Edit: I'm attaching my current code, which is far from correct (preformance is very low and images appear too large first and their size drops a after they all download).

First part of the code (attaches all events to the tiles and adds animation on click):

function attachTileEvents() {
if ($(".tile-flow").size() >= 1) {
    $(window).resize(function () {
        delay(function () {
            resizeTiles();
        }, 100);
    });
    $(document).on("click", ".tile-flow .load-next-page", manualLoadContentDetection);
    $(window).on("scroll", scrollLoadContentDetection);
    $(document).on("touchend", scrollLoadContentDetection);
}
resizeTiles();
$(".tile .contents").each(function () {
    var tile = $(this).parent()[0]
    var mouse = { x: 0, y: 0, down: false };

    var maxRotation = 16;
    var minScale = 0.95;
    var setRotation = function (scaled) {
        //Rotations as percentages 
        var width = tile.offsetWidth;
        var height = tile.offsetHeight;
        var diag = Math.sqrt((width / 2) * (width / 2) + (height / 2) * (height / 2));
        var dist = Math.sqrt((mouse.x - (width / 2)) * (mouse.x - (width / 2)) + (mouse.y - (height / 2)) * (mouse.y - (height / 2)));
        var fract = 1.0;
        if (dist > 0) {
            fract = dist / diag;
        }
        var yRotation = (mouse.x - (width / 2)) / (width / 2);
        var xRotation = (mouse.y - (height / 2)) / (height / 2);

        if (scaled) {
            tile.style.webkitTransform = "rotateX(" + -xRotation * maxRotation + "deg)" + " rotateY(" + yRotation * maxRotation + "deg)" + " scale(" + (minScale + fract * (1 - minScale)) + ")";
            tile.style.mozTransform = "rotateX(" + -xRotation * maxRotation + "deg)" + " rotateY(" + yRotation * maxRotation + "deg)" + " scale(" + (minScale + fract * (1 - minScale)) + ")";
            tile.style.transform = "rotateX(" + -xRotation * maxRotation + "deg)" + " rotateY(" + yRotation * maxRotation + "deg)" + " scale(" + (minScale + fract * (1 - minScale)) + ")";
        } else {
            tile.style.webkitTransform = "rotateX(" + -xRotation * maxRotation + "deg)" + " rotateY(" + yRotation * maxRotation + "deg)";
            tile.style.mozTransform = "rotateX(" + -xRotation * maxRotation + "deg)" + " rotateY(" + yRotation * maxRotation + "deg)";
            tile.style.transform = "rotateX(" + -xRotation * maxRotation + "deg)" + " rotateY(" + yRotation * maxRotation + "deg)";
        }
    }
    var MouseDown = function (e) { mouse.x = e.offsetX; mouse.y = e.offsetY; mouse.down = true; setRotation(true); }
    var MouseUp = function (e) { if (mouse.down) { mouse.down = false; tile.style.webkitTransform = "rotateX(0deg)" + " rotateY(0deg) scale(1.0)"; tile.style.mozTransform = "rotateX(0deg)" + " rotateY(0deg) scale(1.0)"; tile.style.transform = "rotateX(0deg)" + " rotateY(0deg) scale(1.0)"; } }
    var MouseOut = function (e) { mouse.down = false; tile.style.webkitTransform = "rotateX(0deg)" + " rotateY(0deg) scale(1.0)"; tile.style.mozTransform = "rotateX(0deg)" + " rotateY(0deg) scale(1.0)"; tile.style.transform = "rotateX(0deg)" + " rotateY(0deg) scale(1.0)"; }
    var MouseMove = function (e) { mouse.x = e.offsetX; mouse.y = e.offsetY; if (mouse.down == true) { setRotation(false); } }
    $(tile).on("mousemove", MouseMove);
    $(tile).on("mousedown", MouseDown);
    $(tile).on("mouseup", MouseUp);
    $(tile).on("mouseout", MouseOut);
});}

And the main part - resizing:

var TileSizes = { wideWidth: 0, singleWidth: 0, margin: 0 };
function resizeTiles() {
var rowColumnNumber = 2;
var width = $(window).width();
if (width >= 2500) {
    rowColumnNumber = 7;
}
else if (width >= 2000) {
    rowColumnNumber = 6;
} else if (width >= 1600) {
    rowColumnNumber = 5;
} else if (width >= 1280) {
    rowColumnNumber = 4;
} else if (width >= 768) {
    rowColumnNumber = 3;
} else if (width >= 480) {
    rowColumnNumber = 2;
} else {
    rowColumnNumber = 1;
}
var totalWidth = $(".tile-flow").width() - 17; //compensate for the scrollbar
//calculate the margin size : 5% of the flow width
var margin = Math.round(totalWidth * 0.05 / rowColumnNumber);
var wideSize = Math.floor((totalWidth - margin * (rowColumnNumber - 1)) / rowColumnNumber);
var halfSize = Math.floor((wideSize - margin) / 2);
var quaterSize = Math.floor(halfSize * 2.5 / 3);
var heightSize = Math.floor(halfSize * 2 / 2.0);
var doubleHeightSize = heightSize * 2 + margin;
var detailsSize = quaterSize * 2 + margin;
TileSizes.wideWidth = doubleHeightSize;
TileSizes.singleWidth = heightSize;
TileSizes.margin = margin;
$(".big-square-tile").width(doubleHeightSize);
$(".big-square-tile").height(doubleHeightSize);
$(".wide-tile").width(doubleHeightSize);
$(".small-tile").width(halfSize);
$(".tile-flow .col .small-tile:even").css("margin-right", margin);
$(".small-tile").height(heightSize);
$(".wide-tile").height(heightSize);
$(".col").width(doubleHeightSize);
$(".col").css("margin-right", margin);
$(".col:nth-child(" + rowColumnNumber + "n)").css("margin-right", 0);
//all tiles get bottom margin

var how = 0;
$(".wide-tile .contents footer").each(function () {
    if ((how % 4 == 0) || (how % 4 == 1)) {
        $(this).width(TileSizes.singleWidth - 20);
    } else {
        $(this).height(75);
    }
    if (how % 4 == 0) {
        $(this).css("left", TileSizes.wideWidth);
    } else if (how % 4 == 1) {
        $(this).css("left", -TileSizes.singleWidth);
    }
    else if (how % 4 == 2) {
        $(this).css("top", TileSizes.singleWidth);
    } else {
        $(this).css("top", -95);
    }
    how = how + 1;
});

$(".big-square-tile .contents footer").each(function () {
    $(this).height(75);
    if (how % 2 == 0) {
        $(this).css("top", TileSizes.wideWidth);
    } else {
        $(this).css("top", -95);
    }
    how = how + 1;
});

$(".small-tile .contents footer").each(function () {
    $(this).width(TileSizes.singleWidth - 20);
    $(this).height(TileSizes.singleWidth - 20);
    if (how % 4 == 0) {
        $(this).css("left", TileSizes.singleWidth);
    } else if (how % 4 == 1) {
        $(this).css("left", -TileSizes.singleWidth);
    }
    else if (how % 4 == 2) {
        $(this).css("top", TileSizes.singleWidth);
    } else {
        $(this).css("top", -TileSizes.singleWidth);
    }
    how = how + 1;
});

$(".tile").css("margin-bottom", margin);
//resize images    
var imageList = Array();
$(".big-square-tile img").each(function () {
    imageList.push($(this));
    var img = new Image();
    img.onload = function () {
        var originalHeight = this.height;
        var originalWidth = this.width;
        var index = parseInt(this.id.replace("RESIZINGBIG", ""));
        if (originalHeight > originalWidth) {
            imageList[index].css("height", "auto");
            imageList[index].css("width", "100%");
        } else {
            imageList[index].css("height", "100%");
            imageList[index].css("width", "auto");
        }
    }
    img.id = "RESIZINGBIG" + (imageList.length - 1);
    img.src = $(this).attr('src');
});

$(".small-tile img").each(function () {
    imageList.push($(this));
    var img = new Image();
    img.onload = function () {
        var originalHeight = this.height;
        var originalWidth = this.width;
        var index = parseInt(this.id.replace("RESIZINGSMALL", ""));
        if (originalHeight > originalWidth) {
            imageList[index].css("height", "auto");
            imageList[index].css("width", "100%");
        } else {
            imageList[index].css("height", "100%");
            imageList[index].css("width", "auto");
        }
    }
    img.id = "RESIZINGSMALL" + (imageList.length - 1);
    img.src = $(this).attr('src');
});

$(".wide-tile img").each(function () {
    $(this).css("height", "auto");
    $(this).css("width", "100%");
});}

And here is a sample of how the HTML code looks now:

<div class="tile-flow">
    <div class="tile-row">
        <div class="col">
            <div class="tile big-square-tile">
                <div class="contents">
                    <img src="~/Images/Test/5.jpg" />
                    <footer>
                        <h1>Test</h1>
                        <span class="author">by Test</span>
                    </footer>
                </div>
            </div>
        </div>
        <div class="col">
            <div class="tile small-tile">
                <div class="contents">
                    <img src="~/Images/Test/2.jpg" />
                    <footer>
                        <h1>Test</h1>
                        <span class="author">by Test</span>
                    </footer>
                </div>
            </div>
            <div class="tile small-tile">
                <div class="contents">
                    <img src="~/Images/Test/3.jpg" />
                    <footer>
                        <h1>Test</h1>
                        <span class="author">by Test</span>
                    </footer>
                </div>
            </div>
            <div class="tile wide-tile">
                <div class="contents">
                    <img src="~/Images/Test/4.jpg" />
                    <footer>
                        <h1>Test</h1>
                        <span class="author">by Test</span>
                    </footer>
                </div>
            </div>
        </div>
        <div class="col">
            <div class="tile big-square-tile">
                <div class="contents">
                    <img src="~/Images/Test/6.jpg" />
                    <footer>
                        <h1>Test</h1>
                        <span class="author">by Test</span>
                    </footer>
                </div>

            </div>
        </div>
        <div class="col">
            <div class="tile wide-tile">
                <div class="contents">
                    <img src="~/Images/Test/1.jpg" />
                    <footer>
                        <h1>Test</h1>
                        <span class="author">by Test</span>
                    </footer>
                </div>
            </div>
            <div class="tile wide-tile">
                <div class="contents">
                    <img src="~/Images/Test/7.jpg" />
                    <footer>
                        <h1>Test</h1>
                        <span class="author">by Test</span>
                    </footer>
                </div>
            </div>
        </div>
</div>
</div>   
like image 665
Martin Zikmund Avatar asked Apr 18 '13 14:04

Martin Zikmund


1 Answers

If I were you I would use Isotope for the basic layout and add the slide shows and click events along side it. You can insert most any content you like. jQuery Isotope.

Updated Working Model

Full page result

JS

$(function () {

    var $container = $('#container');

    $container.imagesLoaded(function () {
        $container.isotope({
            itemSelector: '.photo'
        });
    });
});


var $container = $('#container');
// initialize Isotope
$container.isotope({
    // options...
    resizable: false, // disable normal resizing
    // set columnWidth to a percentage of container width
    masonry: {
        columnWidth: $container.width() / 5
    }
});

// update columnWidth on window resize
$(window).smartresize(function () {
    $container.isotope({
        // update columnWidth to a percentage of container width
        masonry: {
            columnWidth: $container.width() / 5
        }
    });
});

//click function

    $(function () {
        $('.photo').click(function () {
            $(this).toggleClass('red');
        });
    });

//hover function

    $(function () {
        $('#photo1').hover(function () {
            $('#info1').fadeToggle();
        });
    });

Proof of concept- Animations inside Isotope

Note this animation is total kludge fine tune before using.

 function animatie() {
     var d = 0;
     for (var i = 0; i < 3; ++i) {
         var b = "#info" + i;
         $(b).css('background', 'silver');
         $(b).hide().delay(d).slideDown(1000).delay(3000).slideUp(1000);
         d += 5000;
     }
 }
 animatie();
 window.setInterval(animatie, 15000);

 $(function () {
     for (var i = 0; i < 3; ++i) {
         var z = '.box' + i;
         var divHeight = $(z).height();
         $(z).css('max-height', divHeight + 'px');
         $(z).css('max-height', divHeight + 'px');
         $(z).css('overflow', 'hidden');
     }
 });
 $(window).resize(function () {
     for (var i = 0; i < 3; ++i) {
         var z = '.box' + i;
         var divHeight = $(z).height();
         $(z).css('max-height', divHeight + 'px');
         $(z).css('overflow', 'hidden');
     }
 });

This is a very cool plugin for layout, sorting, and filtering. It will give you the tiles and animations as basic features.

Fluid Isotope

Images Loaded Plugin

Infinite Scroll

Added animation inside Isotope, Check Out the updated jsFiddles above

like image 76
apaul Avatar answered Sep 18 '22 23:09

apaul