<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<div id="container" class="container">
<div class="item">
<a class="item__link" href="javascript:void(0)">
<div class="item__thumbs">
<div class="item__thumb-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479968760/reflection-menu/hand-crafted.svg" alt="Hand Crafted" class="item__thumb js-thumb">
</div>
<div class="item__reflection-wrapper">
<div class="item__reflection-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479968760/reflection-menu/hand-crafted.svg" alt="Hand Crafted" class="item__thumb item__reflection js-reflection">
</div>
</div>
</div>
<div class="item__caption">
Hand <span class="item__caption--em">Crafted</span>
</div>
</a>
</div>
<div class="item">
<a class="item__link" href="javascript:void(0)">
<div class="item__thumbs">
<div class="item__thumb-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479699693/reflection-menu/rainforest_guardian.svg" alt="Rainforest Guardian" class="item__thumb js-thumb">
</div>
<div class="item__reflection-wrapper">
<div class="item__reflection-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479699693/reflection-menu/rainforest_guardian.svg" alt="Rainforest Guardian" class="item__thumb item__reflection js-reflection">
</div>
</div>
</div>
<div class="item__caption">
Rainforest <span class="item__caption--em">Guardian</span>
</div>
</a>
</div>
<div class="item">
<a class="item__link" href="javascript:void(0)">
<div class="item__thumbs">
<div class="item__thumb-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479699692/reflection-menu/sleep.svg" alt="Sleep Benefits" class="item__thumb js-thumb">
</div>
<div class="item__reflection-wrapper">
<div class="item__reflection-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479699692/reflection-menu/sleep.svg" alt="Sleep Benefits" class="item__thumb item__reflection js-reflection">
</div>
</div>
</div>
<div class="item__caption">
Sleep <span class="item__caption--em">Benefits</span>
</div>
</a>
</div>
<div class="item">
<a class="item__link" href="javascript:void(0)">
<div class="item__thumbs">
<div class="item__thumb-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479699693/reflection-menu/videos.svg" alt="Videos Showreel" class="item__thumb js-thumb">
</div>
<div class="item__reflection-wrapper">
<div class="item__reflection-container">
<img src="http://res.cloudinary.com/jasonheecs/image/upload/v1479699693/reflection-menu/videos.svg" alt="Videos Showreel" class="item__thumb item__reflection js-reflection">
</div>
</div>
</div>
<div class="item__caption">
Videos <span class="item__caption--em">Showreel</span>
</div>
</a>
</div>
</div>
<script>
'use strict';
class Menu {
/**
* @constructor
* @param {Element} containerEl
* @return {void}
*/
constructor(containerEl) {
this.containerEl = containerEl;
this.init();
}
/**
* Find the height of the tallest element in a NodeList of elements
* @param {NodeList} els
* @return {number}
*/
findMaxHeight(els) {
let tallestEl = Array.from(els).reduce((tallestEl, currentEl) => {
return tallestEl.offsetHeight > currentEl.offsetHeight ? tallestEl : currentEl;
});
return tallestEl.offsetHeight;
}
/**
* Equalise all the heights of a NodeList of elements based on the tallest element
* @param {NodeList} els
* @return {void}
*/
matchElsHeight(els) {
let maxHeight = this.findMaxHeight(els);
Array.from(els).forEach((el) => {
el.style.height = maxHeight + 'px';
});
}
/**
* Set the hover animations of the thumbnails
*/
setHover() {
Array.from(this.containerEl.querySelectorAll('.item__link')).forEach((el) => {
let thumbEl = el.querySelector('.js-thumb');
let reflectionEl = el.querySelector('.js-reflection');
el.addEventListener('mouseenter', (evt) => {
evt.stopPropagation();
thumbMouseEnterAnimation(thumbEl);
thumbMouseEnterAnimation(reflectionEl, true);
});
el.addEventListener('mouseleave', (evt) => {
evt.stopPropagation();
if (thumbEl.classList.contains('velocity-animating')) {
thumbMouseOutAnimation(thumbEl);
}
if (reflectionEl.classList.contains('velocity-animating')) {
thumbMouseOutAnimation(reflectionEl);
}
});
});
/**
* Animation that happens when mouseenter event fires
* @param {Element} thumbEl
* @param {Boolean} isReflection if thumbEl meant to have scaleY(-1) applied to it before the animation
* @return {void}
*/
function thumbMouseEnterAnimation(thumbEl, isReflection) {
isReflection = isReflection || false;
if (isReflection) {
Velocity.hook(thumbEl, "scaleY", "-1");
}
Velocity(thumbEl, {
translateZ: 0,
translateY: '-18px'
}, {duration: 1000})
.then(
Velocity(thumbEl, {
translateZ: 0,
translateY: '-5px'
}, {
duration: 1000,
loop: true
})
);
}
/**
* Animation that happens when mouseleave event fires
* @param {Element} thumbEl
* @return {void}
*/
function thumbMouseOutAnimation(thumbEl) {
Velocity(thumbEl, "stop", true).then(
Velocity(thumbEl, {
translateZ: 0,
translateY: 0
}, {
duration: 800,
easing: 'easeOutCubic'
})
);
}
}
static create(containerEl) {
return new Menu(containerEl);
}
/**
* Initialize the menu
* @return {void}
*/
init() {
imagesLoaded(this.containerEl, () => {
this.matchElsHeight(this.containerEl.querySelectorAll('.item__thumb-container'));
this.matchElsHeight(this.containerEl.querySelectorAll('.item__reflection-container'));
this.setHover();
});
}
}
Menu.create(document.getElementById('container'));
</script>
body {
display: flex;
align-items: center;
height: 100vh;
background: #ede9e5;
font-family: 'Raleway', sans-serif;
}
a {
outline: 0;
}
.container {
display: flex;
width: 100%;
max-width: 960px;
margin: 100px auto 0;
padding: 0 20px;
border-radius: 30px;
background: #fff;
}
@media screen and (min-width: 768px) {
.container {
padding: 0 40px;
}
}
.item {
flex: 1 1 25%;
margin-top: -100px;
text-align: center;
}
.item__link {
display: inline-block;
position: relative;
text-decoration: none;
}
.item__thumb-container {
display: flex;
align-items: flex-end;
justify-content: center;
}
.item__thumb {
position: relative;
max-width: 100%;
height: auto;
border: 0;
box-sizing: border-box;
}
.item__reflection-wrapper {
position: relative;
height: 155px;
overflow: hidden;
}
.item__reflection-container {
display: flex;
position: absolute;
left: 0;
align-items: flex-start;
justify-content: center;
width: 100%;
}
.item__reflection-container::after {
position: absolute;
top: -10px;
left: -5%;
width: 110%;
height: 120%;
border-radius: 30px;
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.75) 0%, #fff 60%, #fff 100%);
content: '';
}
.item__reflection {
transform: scale3d(1, -1, 1);
filter: blur(2px);
}
.item__caption {
position: absolute;
left: 50%;
transform: translate3d(-50%, -80px, 0);
color: #ad998a;
font-size: 1rem;
font-weight: 500;
text-align: left;
text-transform: uppercase;
word-spacing: 100px;
}
@media screen and (min-width: 768px) {
.item__caption {
font-size: 1.5rem;
}
}
.item__caption--em {
color: #7b685a;
font-weight: 700;
}