CSS3 fluid gallery that loads (and preload) external content (a JSON file) using Javascript.
A CodePen by Juan Cabrera.
CSS3 fluid gallery that loads (and preload) external content (a JSON file) using Javascript.
A CodePen by Juan Cabrera.
#veil | |
#preloader | |
#grid |
function App() { | |
var self = this; | |
// this.feedUrl = "json/media.json"; | |
this.debug = true; | |
this.cellElement = null; | |
this.window = $(window); | |
this.grid = $("#grid"); | |
this.fadeOutMs = 500; | |
this.unFoldMs = 300; | |
this.preloader = $("#preloader"); | |
this.veil = $("#veil"); | |
this.currentMedia = null; | |
// initialize | |
this._init = function() { | |
// load media feed | |
self.loadFeed(function(d) { | |
// add elements to the DOM | |
self.addToDOM(d); | |
self.cellElement = $(".cell"); | |
self.window.on('resize', self.resizeHandler).resize(); | |
// hide loader and show grid | |
self.preloader.addClass('o'); | |
window.setTimeout(function() { | |
self.grid.addClass('loaded'); | |
}, self.fadeOutMs); | |
}); | |
// prepare pop state to load medias | |
window.onpopstate = function(e) { | |
if (e.state) self.loadMedia(e.state); | |
} | |
// click on veil will close the current media | |
self.veil.on('click', function(e) { | |
e.preventDefault(); | |
if (self.currentMedia) self.closeMedia(self.currentMedia); | |
}); | |
// hotkeys | |
$(document).on('keydown', function(event) { | |
switch (event.keyCode) { | |
// ESC | |
case 27: | |
// close the current media | |
if (self.currentMedia != null) self.closeMedia(self.currentMedia); | |
return false; | |
} | |
}); | |
} | |
// load media.json | |
this.loadFeed = function(callback) { | |
callback(media); | |
// $.ajax({ | |
// url: self.feedUrl, | |
// type: 'GET', | |
// dataType: 'json', | |
// complete: function(xhr, textStatus) { | |
// }, | |
// success: function(data, textStatus, xhr) { | |
// callback(data); | |
// }, | |
// error: function(xhr, textStatus, errorThrown) { | |
// } | |
// }); | |
} | |
// add media elements to the DOM | |
this.addToDOM = function(d) { | |
var htmlImage = '<a href="#" class="cell image" data-id-media="{id-media}"><div class="detail"><div class="close"><span></span></div><div class="image-detail"></div><div class="tl"></div><div class="bl"></div><div class="tr"></div></div><div class="info"><div class="w"><h2>{title}</h2></div></div><div class="image"><img src="{img-src}"></div></a>', | |
nroMedia = d.media.length; | |
$.each(d.media, function(i, v) { | |
self.grid.append(htmlImage.replace('{img-src}', v.url).replace('{title}', v.title).replace('{id-media}', v.id)); | |
// moving the detail view if media element is at the end of the grid | |
if ((i + 1) % 5 == 0) $("a[data-id-media='" + v.id + "']").find('.detail:first').addClass('l'); | |
if (i >= (nroMedia - 5)) $("a[data-id-media='" + v.id + "']").find('.detail:first').addClass('t'); | |
// event to load media | |
$("a[data-id-media='" + v.id + "']").on('click', function(e) { | |
e.preventDefault(); | |
console.log(v); | |
self.loadMedia(v); | |
}); | |
// doesn't show the image until it's fully loaded, and then will fade in. | |
self.grid.find("img:last").on('load', function() { | |
$(this).closest('a.cell').addClass("loaded"); | |
window.setTimeout(function() { | |
$(this).closest('a.cell').addClass('no-bg'); | |
}, self.fadeOutMs); | |
}) | |
}); | |
} | |
// load media and set push state | |
this.loadMedia = function(m) { | |
window.history.pushState(m, m.title + m.id, "/" + m.id + ".html"); | |
if (m.id != self.currentMedia) { | |
self.openMedia(m); | |
} else { | |
if (self.currentMedia != null) self.closeMedia(self.currentMedia); | |
} | |
} | |
// open a media element | |
this.openMedia = function(m) { | |
var $cellMedia = $("a[data-id-media='" + m.id + "']"), | |
$detail = $cellMedia.find('.detail'), | |
htmlImage = '<img src="{img-src}" />', | |
waitForIt = 0; | |
// if a media element is open will close it first | |
if (self.currentMedia != null) { | |
self.closeMedia(self.currentMedia); | |
waitForIt = (self.unFoldMs * 2) + (self.fadeOutMs * 2); | |
} | |
window.setTimeout(function() { | |
// if press back button in browser it will scroll to the position of the media element | |
$('body, html').animate({scrollTop:$cellMedia.offset().top - 100}, 600); | |
$detail.find('.image-detail:first').append(htmlImage.replace('{img-src}', m.url)); | |
$cellMedia.addClass('ov'); | |
$detail.addClass('open o'); | |
self.veil.addClass('o'); | |
self.currentMedia = m.id; | |
}, waitForIt); | |
} | |
// close a media element | |
this.closeMedia = function(m) { | |
var $cellMedia = $("a[data-id-media='" + m + "']"), | |
$detail = $cellMedia.find('.detail'); | |
$detail.addClass('close'); | |
window.setTimeout(function() { | |
$detail.removeClass('open close o'); | |
$detail.find('.image-detail:first').html(''); | |
self.currentMedia = null; | |
}, (self.unFoldMs * 2) + (self.fadeOutMs * 2)); | |
window.setTimeout(function() { | |
self.veil.removeClass('o'); | |
}, self.fadeOutMs); | |
} | |
// resize handler for fluid grid | |
this.resizeHandler = function() { | |
var c = 0, | |
r = 0, | |
widthCell = Math.ceil(self.window.width() / 5); | |
$.each(self.cellElement, function(i, v) { | |
$(v).css({'top': (r * widthCell), 'left': (c * widthCell), 'width': widthCell, 'height': widthCell}); | |
c++; | |
if (c % 5 == 0) { | |
c = 0; | |
r++; | |
} | |
}); | |
} | |
self._init(); | |
} | |
var Gallery; | |
$(window).load(function() { | |
Gallery = new App(); | |
}); |
@import "compass"; | |
$fade-in-ms: 500ms; | |
$fade-out-ms: 600ms; | |
$unfold-ms: 300ms; | |
$rotate-ms: 150ms; | |
$din-light: "din-light"; | |
$din-medium: "din-medium"; | |
@font-face { | |
font-family: 'din-light'; | |
src: url('http://labs.juan.me/gallery/css/fonts/din-light-webfont.eot'); | |
src: url('http://labs.juan.me/gallery/css/fonts/din-light-webfont.eot?#iefix') format("embedded-opentype"), url('http://labs.juan.me/gallery/css/fonts/din-light-webfont.woff') format("woff"), url('http://labs.juan.me/gallery/css/fonts/din-light-webfont.ttf') format("truetype"), url('http://labs.juan.me/gallery/css/fonts/din-light-webfont.svg#din-bold-webfont') format("svg"); | |
font-weight: normal; | |
font-style: normal; | |
} | |
@font-face { | |
font-family: 'din-medium'; | |
src: url('http://labs.juan.me/gallery/css/fonts/din-medium-webfont.eot'); | |
src: url('http://labs.juan.me/gallery/css/fonts/din-medium-webfont.eot?#iefix') format("embedded-opentype"), url('http://labs.juan.me/gallery/css/fonts/din-medium-webfont.woff') format("woff"), url('http://labs.juan.me/gallery/css/fonts/din-medium-webfont.ttf') format("truetype"), url('http://labs.juan.me/gallery/css/fonts/din-medium-webfont.svg#din-bold-webfont') format("svg"); | |
font-weight: normal; | |
font-style: normal; | |
} | |
$din-light: "din-light"; | |
$din-medium: "din-medium"; | |
#preloader { | |
position: absolute; | |
width: 24px; | |
height: 24px; | |
left: 50%; | |
top: 50%; | |
margin: -12px 0 0 -12px; | |
background: url(http://labs.juan.me/gallery/images/loading-24.gif) no-repeat center center; | |
@include transition($fade-out-ms, opacity, ease-out); | |
@include opacity(1); | |
&.o { | |
@include opacity(0); | |
} | |
} | |
#veil { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 0; | |
background: rgba(0, 0, 0, 0.84); | |
z-index: 7; | |
@include transition-property(opacity, height); | |
@include transition-duration($unfold-ms * 3, 0s); | |
@include transition-timing-function(ease-out); | |
@include transition-delay(0s, $unfold-ms * 3); | |
@include opacity(0); | |
&.o { | |
@include transition-delay(0s, 0s); | |
@include opacity(1); | |
height: 100%; | |
} | |
} | |
#grid { | |
width: 100%; | |
height: 100%; | |
@include transition($fade-in-ms, opacity, ease-out); | |
@include opacity(0); | |
font-size: 20px; | |
&.loaded { | |
@include opacity(1); | |
} | |
.cell { | |
position: absolute; | |
width: 256px; | |
height: 256px; | |
overflow: hidden; | |
background: url(http://labs.juan.me/gallery/images/loading-10.gif) no-repeat center center; | |
&.ov { | |
overflow: visible; | |
} | |
.detail { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 200%; | |
height: 200%; | |
@include opacity(0); | |
&.l { | |
left: -100%; | |
} | |
&.t { | |
top: -100%; | |
} | |
.close { | |
position: absolute; | |
top: 3%; | |
right: 3%; | |
width: 35px; | |
height:35px; | |
z-index: 9; | |
background: black; | |
@include opacity(0); | |
@include transition($fade-in-ms, opacity, ease-out, ($unfold-ms * 3) + $fade-in-ms); | |
span { | |
position: absolute; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
background: url(http://labs.juan.me/gallery/images/icon-close.png) no-repeat center center; | |
@include transition($rotate-ms, all, ease-out); | |
&:hover { | |
@include rotate(90deg); | |
} | |
} | |
} | |
.image-detail { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: white; | |
@include opacity(0); | |
z-index: 8; | |
@include transition($fade-out-ms, opacity, ease-out, $unfold-ms * 3); | |
img { | |
width: 100%; | |
} | |
} | |
&.o { | |
@include opacity(1); | |
} | |
&.open { | |
z-index: 7; | |
@include perspective(500); | |
@include perspective-origin(25%, 0%); | |
.close { | |
@include opacity(.5); | |
} | |
.tl { | |
@include transform(rotateX(0deg)); | |
@include opacity(1); | |
} | |
.bl { | |
@include transform(rotateX(0deg)); | |
@include opacity(1); | |
} | |
.tr { | |
@include transform(rotateY(0deg)); | |
@include opacity(1); | |
} | |
.image-detail { | |
@include opacity(1); | |
} | |
} | |
&.close { | |
.close { | |
@include transition($fade-out-ms, opacity, ease-out, 0s); | |
@include opacity(0); | |
} | |
.image-detail { | |
@include transition($fade-out-ms, opacity, ease-out, 0s); | |
@include opacity(0); | |
} | |
.tr { | |
@include transition($unfold-ms, all, linear, $fade-out-ms); | |
@include transform(rotateY(90deg)); | |
@include opacity(0); | |
} | |
.bl { | |
@include transition($unfold-ms, all, linear, $fade-out-ms + $unfold-ms); | |
@include transform(rotateX(-90deg)); | |
@include opacity(0); | |
} | |
.tl { | |
@include transition($unfold-ms, all, linear, $fade-out-ms + ($unfold-ms * 2)); | |
@include transform(rotateX(-90deg)); | |
@include opacity(0); | |
} | |
} | |
.tl { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 50%; | |
height: 50%; | |
background: white; | |
@include transition($unfold-ms, all, linear); | |
@include opacity(0); | |
@include transform-origin(0%, 0%); | |
@include transform(rotateX(-90deg)); | |
z-index: 6; | |
} | |
.bl { | |
position: absolute; | |
top: 50%; | |
left: 0; | |
width: 50%; | |
height: 50%; | |
background: white; | |
@include transition($unfold-ms, all, linear, $unfold-ms ); | |
@include opacity(0); | |
@include transform-origin(0%, 0%); | |
@include transform(rotateX(-90deg)); | |
z-index: 5; | |
} | |
.tr { | |
position: absolute; | |
top: 0; | |
left: 50%; | |
width: 50%; | |
height: 100%; | |
background: white; | |
z-index: 4; | |
@include transition($unfold-ms, all, linear, ($unfold-ms * 2)); | |
@include opacity(0); | |
@include transform-origin(0%, 0%); | |
@include transform(rotateY(90deg)); | |
} | |
} | |
&.no-bg { | |
background: none; | |
} | |
&.loaded { | |
.image { | |
img { | |
@include opacity(1); | |
} | |
} | |
.info { | |
display: table; | |
} | |
} | |
.info { | |
display: none; | |
position: absolute; | |
height: inherit; | |
width: inherit; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
z-index: 2; | |
background: rgba(255, 255, 255, 0.8); | |
@include opacity(0); | |
@include transition($fade-out-ms, opacity, ease-out); | |
&:hover { | |
@include opacity(1); | |
} | |
.w { | |
display: table-cell; | |
vertical-align: middle; | |
h2 { | |
color: #333; | |
background: none; | |
text-align: center; | |
@include font($din-light, 100%); | |
padding: 0 5% 0 5%; | |
} | |
} | |
} | |
.image { | |
position: absolute; | |
height: inherit; | |
width: inherit; | |
top: 0; | |
right: 0; | |
bottom: 0; | |
left: 0; | |
z-index: 1; | |
img { | |
width: 100%; | |
@include transition($fade-out-ms, opacity, ease-out); | |
@include opacity(0); | |
} | |
} | |
} | |
} |