Fluidbox Demo

by Terry Mun

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/3.0.4/jquery.imagesloaded.min.js"></script>
<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>. This project was originally initiated as a personal challenge to replicate Medum's lightbox module, but it soon developed into a full-fledged jQuery plugin. You can follow the links below to read how I've tackled the challenge, or view the jQuery plugin on GitHub:</p>
  <div class="cards">
    <a href="https://medium.com/coding-design/9c7fe9db92c7">
      <svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
	 x="0px" y="0px" viewBox="0 0 200 200" xml:space="preserve">
        <g>
          <path d="M0.001,0v200h200V0H0.001z M148.913,72.752h-3.869c-1.437,0-3.095,1.879-3.095,3.206v48.081
		c0,1.328,1.658,3.316,3.095,3.316h3.869v11.164h-34.708v-11.164h6.964V76.842h-0.332l-17.133,61.677H90.439L73.528,76.842h-0.332
		v50.513h7.295v11.164H51.089v-11.164h3.758c1.548,0,3.206-1.988,3.206-3.316V75.958c0-1.327-1.658-3.206-3.206-3.206h-3.758V61.146
		h36.697l12.048,44.877h0.332l12.16-44.877h36.587V72.752z"/>
        </g>
      </svg>
      <span>Read the tutorial on Medium</span>
    </a>
		<a href="https://github.com/terrymun/Fluidbox">
      <svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 850.39 850.39" xml:space="preserve">
        <g>
          <path fill-rule="evenodd" fill="#191717"...

SCSS

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: #333;
  background-image: linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,.25) 100%);
  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;
  
  &:before {
    background-image:
      linear-gradient(115deg, rgba(255,255,255,.025) 0%, rgba(255,255,255,.05) 50%, rgba(255,255,255,0) 50%),
      linear-gradient(to left, #005952 0%, #005E20 100%);
    content: "";
    opacity: 0;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    transition: all .25s ease-in-out;
  }
  
  &:hover:before {
    opacity: 1;
  }
  
  & > * {
    position: relative;
  }
  
  & 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);
  }
  
  &.note {
    background-color: #7BCDC8;
    border-color: #00A99D;
  }
}
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...

JavaScript

// Wait for DOM
$(function (){
  
  // Global variables
  var $fb = $('a[data-fluidbox]'),
      vpRatio;
  
  // Add class
  $fb.addClass('fluidbox');
  
  // Create fluidbox modal background
  $('body').append('<div id="fluidbox-overlay" />');
 
  // The following events will force FB to close
  var closeFb = function (){
        $('a[data-fluidbox].fluidbox-opened').trigger('click');
      },
      positionFb = function ($activeFb){
        // Get elements
        var $img = $activeFb.find('img'),
            $ghost = $activeFb.find('.fluidbox-ghost');
        
        // 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 ghost element
        // For offsetX and Y, we round to one decimal places
				// For scale, we round to three decimal places
				$ghost.css({
					'transform': 'translate('+parseInt(offsetX*10)/10+'px,'+parseInt(offsetY*10)/10+'px) scale('+parseInt(scale*1000)/1000+')'
				});
      }

  // Close Fluidbox when overlay is closed
  $('#fluidbox-overlay').click(closeFb);
  
  // 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 =...