#js30.05 Flex Panels Image Gallery

Wes Bos’ Javascript30 course Project #5 is a flex panel image gallery. The concept relies primarily on the CSS Flexbox module, while the javascript consists simply of event listeners and class-toggling. Again, I took the initial version and extended its functionality a little, which I’ll describe below. The mechanism could easily be adapted for innumerable uses, and I have already a few in mind.

The design concept is, we have a fixed number of divs, each of which fills the vertical space, has a background image, and three children elements for texts. The text divs are vertically stacked within each panel, and the first and third (i.e., the top and bottom) are off-screen via CSS transform > translate property. A ‘click’ event on a panel triggers a CSS transition which moves the off-screen text into view while the panel itself is horizontally enlarged, shrinking the others. So this was a pretty straightforward exercise, since I’m already quite fammiliar with CSS flexbox

The markup is as follows:


<div class="panels">
   <div class="panel panel1">
      <p>[some text here]</p>
      <p>[some text here]</p>
      <p>[some text here]</p>
   </div>
   <div class="panel panel2">
      <p>[some text here]</p>
      <p>[some text here]</p>
      <p>[some text here]</p>
   </div>
   … (etc. more panels)
   </div>
</div>

… and the pertinent CSS is as follows:


.panels   {
   display: flex;
   flex-flow: row nowrap;
   justify-content: space-around;
   width: 100%;
   min-height: 100vh;
   overflow: hidden;
}
.panel   {
   flex: 1; /* evenly share available space */
   display: flex;
   flex-flow: column;
   text-align: center;
   /* Safari transitionend event.propertyName === flex */
   /* Chrome + FF transitionend event.propertyName === flex-grow */
   transition: 
      flex .6s cubic-bezier(.6,-.18, .6,-.12),
      background .24s;
   font-size: 21px;
   background-size: cover;
   background-position: center;
}

.panel1 { background-image: url([snip]); }
(.panel2, etc. ... )

.panel > * {
   width: 100%;
   flex: 1 0 auto;
   display: flex;
   justify-content: center;
   align-content: center;
   align-items: center;
   transition: transform .6s;
}

/* move 1st, 3rd els vertically off-screen (up, down) */
.panel > *:first-child   { transform: translateY(-100%); }
.panel > *:last-child   { transform: translateY(100%); }

.panel.open-active > *:first-child   { transform: translateY(0); }
.panel.open-active > *:last-child   { transform: translateY(0); }

regarding which:

  • each div.panel is a flex item (div.panels is flex parent), sharing available space equally per ‘flex: 1 0 auto;”
  • div.panel is also a flex parent, however its flex flow property is set to ‘column’ so that their children (paragraphs) are stacked
  • the ‘align-items: center;’ rule in the .panel > * selector ensures the vertical layout of the stacked paragraph elements

… and the javascript (in the tutorial version) is simply an event listener and class setter:


   const panels = document.querySelectorAll('.panel');
   panels.forEach(panel => panel.addEventListener('click', toggleOpen));
   panels.forEach(panel => panel.addEventListener('transitionend', transitionActive));

   function toggleOpen() {
      this.classList.toggle("open");
   };
   function transitionActive(e) {
      console.log(e.propertyName); // e.flex-grow, e.font-size
      // not sure why the transition property must be specifically targeted, but it do:
      if (e.propertyName.includes("flex")) {
         this.classList.toggle("open-active");
      }
   };

The tutorial version goes only so far as to add the active classes to each panel, but I preferred to modify this so that an active panel will have its active class removed, either on click on an already active panel or on click of another (non-active) panel. So my version has the following javascript:


   const panels = document.querySelectorAll('.panel');
   panels.forEach(panel => panel.addEventListener('click', toggleOpen));
   panels.forEach(panel => panel.addEventListener('transitionend', transitionActive));

   function toggleOpen(panel) {
      var thisPanel = this;
      if (this.classList.contains("open")) {
         this.classList.remove("open");
      }
      else {
         panels.forEach(panel => panel.classList.remove("open"));
         thisPanel.classList.add("open");
      }
   };

   function transitionActive(e) {
      if (e.propertyName.includes("flex")) {
         this.classList.toggle("open-active");
      }
   };

flex panel gallery (tutorial vsn)
flex panel gallery (my vsn)

As is often the case, I suspect my version could be sweetened but in any case I found this exercise a very useful and illuminating lesson, especially with regard to the classList API.