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.