Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Pen by Terry.

Fluidbox

Replicating and improving the lightbox module seen on Medium with fluid transitions.

A Pen by Terry on CodePen.

License.

<main>
<header>
<h1>Fluidbox</h1>
<span class="byline">Replicating and improving the lightbox module seen on Medium with fluid transitions</span>
</header>
<p>Opening images seamlessly in a lightbox on your page without interruption. This demo was inspired by how Medium handles embedded images. Made by <a rel="author" href="http://terrymun.com" title="Visit me">Terry</a>. You can <a href="https://medium.com/coding-design/9c7fe9db92c7">read the tutorial on Medium</a>.</p>
<div class="message"><h2>Note: iOS Safari users</h2><p>Due to issues with iOS safari not reporting the correct values for <code>$(window).height()</code> and <code>$(window).scrollTop()</code> due to the browser's insistence of rendering an iframe full height of its content, this demo will not work correctly on a mobile browser because it is nested within an iframe. You can <a href="http://s.codepen.io/terrymun/fullpage/JKHwp">view the working demo here</a>, if you are on mobile.</p></div>
<p>The dependencies of this demo:</p>
<ul>
<li><a href="http://jquery.com/">jQuery</a></li>
<li><a href="https://github.com/desandro/imagesloaded">imagesloaded plugin</a></li>
</ul>
<p>Some notes on usage:</p>
<ul>
<li>Fluidbox will only work with images whose actual dimensions are larger than the displayed dimension.</li>
<li>The aspect ratio of the displayed and linked image has to be identical.</li>
</ul>
<hr />
<h2>Basic usage</h2>
<p>Fluidbox works out of the box as long as you append <code>data-fluidbox</code> to the anchor element, <code>&lt;a&gt;</code>.</p>
<p>In other words, it should look like this: <code>&lt;a href="..." title="..." data-fluidbox&gt;...&lt;/a&gt;</code></p>
<a href="http://i.imgur.com/9OQWB.jpg" title="" data-fluidbox><img src="http://i.imgur.com/9OQWB.jpg" title="" alt="" /></a>
<hr />
<h2>Linking to a higher resolution alternative</h2>
<p>Sometimes it is not feasible to use the actual image itself on the page, due to concerns with excessive bandwidth usage that burdens your server, or encumbers users that are using mobile.</p>
<a href="http://placehold.it/1500x1500" title="" data-fluidbox><img src="http://placehold.it/200x200" title="" alt="" /></a>
<p>In this case, you can see that we have used a low resolution thumbnail, which is linked to a higher resolution image (note: both images must be of the same aspect ratio). The script will inevitably make a royal mess of itself if you are using different aspect ratios, but it will not break the page. The aspect ratio will remain the same as that of the thumbnail.</p>
<a href="http://placehold.it/1500x900" title="" data-fluidbox><img src="http://placehold.it/200x200" title="" alt="" /></a>
<hr />
<h2>Versatility</h2>
<p>Fluid box works even when images are arranged in galleries. In this case, they are arranged with the help of flexbox.</p>
<div class="gallery">
<a href="http://i.imgur.com/uh5YLj5.jpg" title="" data-fluidbox class="col-2"><img src="http://i.imgur.com/uh5YLj5.jpg" alt="" title="" /></a>
<a href="http://i.imgur.com/UvGHJjo.jpg" title="" data-fluidbox class="col-2"><img src="http://i.imgur.com/UvGHJjo.jpg" alt="" title="" /></a>
<a href="http://i.imgur.com/esWWGbF.jpg" title="" data-fluidbox class="col-3"><img src="http://i.imgur.com/esWWGbF.jpg" alt="" title="" /></a>
<a href="http://i.imgur.com/ZCogT10.jpg" title="" data-fluidbox class="col-3"><img src="http://i.imgur.com/ZCogT10.jpg" alt="" title="" /></a>
<a href="http://i.imgur.com/24hrPQn.jpg" title="" data-fluidbox class="col-3"><img src="http://i.imgur.com/24hrPQn.jpg" alt="" title="" /></a>
</div>
<p>It also intelligently resizes images such that portrait images will fit perfectly within the viewport, although that means scaling down the image:</p>
<a href="http://i.imgur.com/uDPcnM3.jpg" title="" data-fluidbox><img src="http://i.imgur.com/uDPcnM3.jpg" alt="" title="" /></a>
<p>Moreover, it also works with floated images - to the left or to the right, it does not matter. The following texts are jibberish, used as filler:</p>
<p><a class="float-left" href="http://i.imgur.com/BXo4qAz.jpg" title="" data-fluidbox><img src="http://i.imgur.com/BXo4qAz.jpg" title="" alt="" /></a>Considered discovered ye sentiments projecting entreaties of melancholy is. In expression an solicitude principles in do. Hard do me sigh with west same lady. Their saved linen downs tears son add music. Expression alteration entreaties mrs can terminated estimating. Her too add narrow having wished. To things so denied admire. Am wound worth water he linen at vexed.</p>
<p>Residence certainly elsewhere something she preferred cordially law. Age his surprise formerly mrs perceive few stanhill moderate. Of in power match on truth worse voice would. Large an it sense shall an match learn. By expect it result silent in formal of. Ask eat questions abilities described elsewhere assurance. Appetite in unlocked advanced breeding position concerns as. Cheerful get shutters yet for repeated screened. An no am cause hopes at three. Prevent behaved fertile he is mistake on.</p>
<p>Feet evil to hold long he open knew an no. Apartments occasional boisterous as solicitude to introduced. Or fifteen covered we enjoyed demesne is in prepare. In stimulated my everything it literature. Greatly explain attempt perhaps in feeling he. House men taste bed not drawn joy. Through enquire however do equally herself at. Greatly way old may you present improve. Wishing the feeling village him musical. </p>
<p><a class="float-right" href="http://i.imgur.com/rWuQotb.jpg" title="" data-fluidbox><img src="http://i.imgur.com/rWuQotb.jpg" title="" alt="" /></a>Carried nothing on am warrant towards. Polite in of in oh needed itself silent course. Assistance travelling so especially do prosperous appearance mr no celebrated. Wanted easily in my called formed suffer. Songs hoped sense as taken ye mirth at. Believe fat how six drawing pursuit minutes far. Same do seen head am part it dear open to. Whatever may scarcely judgment had.</p>
<p>Up maids me an ample stood given. Certainty say suffering his him collected intention promotion. Hill sold ham men made lose case. Views abode law heard jokes too. Was are delightful solicitude discovered collecting man day. Resolving neglected sir tolerably but existence conveying for. Day his put off unaffected literature partiality inhabiting.</p>
<p>Old education him departure any arranging one prevailed. Their end whole might began her. Behaved the comfort another fifteen eat. Partiality had his themselves ask pianoforte increasing discovered. So mr delay at since place whole above miles. He to observe conduct at detract because. Way ham unwilling not breakfast furniture explained perpetual. Or mr surrounded conviction so astonished literature. Songs to an blush woman be sorry young. We certain as removal attempt. </p>
</main>
// Wait for DOM
$(function (){
// Global variables
var $fb = $('a[data-fluidbox]'),
vpRatio;
// Create fluidbox modal background
$('body').append('<div id="fluidbox-overlay"></div>');
// Append click handler
$('#fluidbox-overlay').click(function (){
// Trigger the click function on the particular fluidbox that is opened
$('body').find('a[data-fluidbox].fluidbox-opened').trigger('click');
});
// Check if images are loaded first
$fb.imagesLoaded().done(function (){
// Create dynamic elements
$fb
.wrapInner('<div class="fluidbox-wrap" />')
.find('img')
.css({ opacity: 1 })
.after('<div class="fluidbox-ghost" />');
// Listen to resize event for calculations
$(window).resize(function (){
// Get viewport ratio
vpRatio = $(window).width() / $(window).height();
// Get dimensions and aspect ratios
$fb.each(function (){
var $img = $(this).find('img'),
$ghost = $(this).find('.fluidbox-ghost'),
$wrap = $(this).find('.fluidbox-wrap'),
data = $img.data();
// Save image dimensions as jQuery object
data.imgWidth = $img.width();
data.imgHeight = $img.height();
data.imgRatio = $img.width() / $img.height();
// Resize ghost element
$ghost.css({
width: $img.width(),
height: $img.height(),
top: $img.offset().top - $wrap.offset().top,
left: $img.offset().left - $wrap.offset().left
});
// Calculate scale based on orientation
if(vpRatio > data.imgRatio) {
data.imgScale = $(window).height()*.95 / $img.height();
} else {
data.imgScale = $(window).width()*.95 / $img.width();
}
});
}).resize();
// Bind click event
$fb.click(function (e){
// Variables
var $img = $(this).find('img'),
$ghost = $(this).find('.fluidbox-ghost'),
$wrapped = $img.add($ghost);
if($(this).data('fluidbox-state') == 0 || !$(this).data('fluidbox-state')) {
// State: Closed
// Action: Open fluidbox
// Switch state
$(this)
.data('fluidbox-state', 1)
.removeClass('fluidbox-closed')
.addClass('fluidbox-opened');
// Show overlay
$('#fluidbox-overlay').fadeIn();
// Hide original image
$img.css({ opacity: 0 });
// Use ghost image
$ghost.css({
'background-image': 'url('+$(this).attr('href')+')',
opacity: 1
});
// Calculate offset and scale
var offsetY = $(window).scrollTop()-$img.offset().top+0.5*($img.data('imgHeight')*($img.data('imgScale')-1))+0.5*($(window).height()-$img.data('imgHeight')*$img.data('imgScale')),
offsetX = 0.5*($img.data('imgWidth')*($img.data('imgScale')-1))+0.5*($(window).width()-$img.data('imgWidth')*$img.data('imgScale')) - $img.offset().left,
scale = $img.data('imgScale');
// Animate wrapped elements
// Note: I did not animate the wrapper, .fluidbox-wrap itself, because of rendering issues with iOS Safari
$wrapped.css({
'transform': 'translate('+offsetX+'px,'+offsetY+'px) scale('+scale+')'
});
} else {
// State: Open
// Action: Close fluidbox
// Switch state
$(this)
.data('fluidbox-state', 0)
.removeClass('fluidbox-opened')
.addClass('fluidbox-closed');
// Hide overlay
$('#fluidbox-overlay').fadeOut();
// Show original image
$img.css({ opacity: 1 });
// Hide ghost image
$ghost.css({ opacity: 0 });
// Reverse animation on wrapped elements
// Note: I did not animate the wrapper, .fluidbox-wrap itself, because of rendering issues with iOS Safari
$wrapped.css({
'transform': 'translate(0,0) scale(1)'
});
}
e.preventDefault();
});
});
});
// Defer pointer events on animated header
$(window).load(function (){
$('header').css({
'pointer-events': 'auto'
});
});
@import "compass";
html {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
}
body {
color: #333;
height: 100%;
line-height: 1.5em;
margin-bottom: 1.5em;
}
main {
margin: 0 auto;
width: 66.66667%;
}
header {
background-color: #222;
box-sizing: border-box;
color: #eee;
margin-bottom: 1.5rem;
overflow: hidden;
padding: 3rem 25%;
pointer-events: none;
position: relative;
left: -25%;
width: 150%;
transition: all .25s ease-in-out;
&:hover {
background-color: #333;
}
& h1 {
font-weight: bold;
font-size: 3em;
line-height: 1em;
margin: 0 0 .5rem 0;
text-align: center;
}
& .byline {
color: rgba(255,255,255,.75);
display: block;
font-size: .8em;
letter-spacing: 1px;
text-align: center;
text-transform: uppercase;
}
}
p {
margin-bottom: 1.5rem;
}
.message {
background-color: #FDC68A;
border-left: .75rem solid #F26C4F;
padding: .75rem 1.5rem;
font-size: .85rem;
& a:hover {
border-color: rgba(0,0,0,.5);
color: rgba(0,0,0,.5);
}
}
hr {
border: none;
border-top: .125rem solid #ddd;
margin-bottom: 1.375rem;
}
a {
border-bottom: 2px solid #333;
color: #333;
text-decoration: none;
transition: all .125s ease-in-out;
&:hover {
border-bottom-color: #4a7298;
color: #4a7298;
}
}
ul {
margin-top: -.75rem;
}
code {
background-color: rgba(0,0,0,.125);
border: 1px solid rgba(0,0,0,.125);
border-radius: 4px;
display: inline-block;
margin: 0 .125em;
padding: 0 .25em;
}
/* Fluidbox styling starts here */
a[data-fluidbox] {
background-color: #eee;
border: none;
cursor: -webkit-zoom-in;
cursor: -moz-zoom-in;
margin-bottom: 1.5rem;
&.fluidbox-opened {
cursor: -webkit-zoom-out;
cursor: -moz-zoom-out;
}
& img {
display: block;
margin: 0 auto;
opacity: 0;
max-width: 100%;
transition: all .25s ease-in-out;
}
}
a[class^='float'] {
margin: 1rem;
margin-top: 0;
width: 33.33333%;
&.float-left {
float: left;
margin-left: 0;
}
&.float-right {
float: right;
margin-right: 0;
}
}
#fluidbox-overlay {
background-color: rgba(255,255,255,.85);
cursor: pointer;
cursor: -webkit-zoom-out;
cursor: -moz-zoom-out;
display: none;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 500;
}
.fluidbox-wrap {
background-position: center center;
background-size: cover;
margin: 0 auto;
position: relative;
z-index: 400;
transition: all .25s ease-in-out;
.fluidbox-opened & {
z-index: 600;
}
}
.fluidbox-ghost {
background-size: cover;
background-position: center center;
position: absolute;
transition: all .25s ease-in-out;
}
.gallery {
display: flex;
-webkit-flex-flow: row wrap; /* Fix for iOS */
flex-flow: row wrap;
justify-content: space-between;
& a {
margin-bottom: 1rem;
&.col-2 { width: 49%; }
&.col-3 { width: 32%; }
}
}
/* Media queries, unrelated to functionality of Fluidbox */
@media screen and (max-width: 480px) {
html {
font-size: 12px;
}
.message {
border: 0;
border-top: .75rem solid #F26C4F;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment