JavaScript
// **************************************************
// Locations Hover
// **************************************************
const locationsHover = (className) => {
// get the required elements
const locationsContainer = document.querySelector(className)
// guard clause to make sure the '.locations-container' element exists on the page
if (locationsContainer == null) {
return
}
// get the required elements
const locationItem = locationsContainer.querySelectorAll('.locations__item')
locationItem.forEach((element, index) => {
// count the index but +1 so we don't start with '0'
index = index + 1
// get the current element's previous and next elements
let previousElement = element.previousElementSibling
let nextElement = element.nextElementSibling
// expand
function mouseoverFocus() {
// if even, else odd
if (index % 2 === 0) {
element.classList.add('locations__item--expand')
if (previousElement) {
previousElement.classList.add('locations__item--shrink')
}
} else {
element.classList.add('locations__item--expand')
if (nextElement) {
nextElement.classList.add('locations__item--shrink')
}
}
}
element.addEventListener('mouseover', mouseoverFocus)
element.addEventListener('focus', mouseoverFocus)
// shrink
function mouseoutBlur() {
// if even, else odd
if (index % 2 === 0) {
element.classList.remove('locations__item--expand')
if (previousElement) {
previousElement.classList.remove('locations__item--shrink')
}
} else {
element.classList.remove('locations__item--expand')
if (nextElement) {
nextElement.classList.remove('locations__item--shrink')
}
}
}
element.addEventListener('mouseout', mouseoutBlur)
element.addEventListener('blur', mouseoutBlur)
// get required elements
const locationsInfo = element.querySelector('.locations__info')
const locationsMore = element.querySelector('.locations__more')
// get the width of each '.locations__item' element, add a percentage to it to make it match the width of the "expanded" content, then use the value to set the width of the '.locations__more' element
const viewportWidth = window.innerWidth
const locationsItemWidth = element.offsetWidth
console.log('viewportWidth = ' + viewportWidth)
if (viewportWidth >= 1024) {
const percentage = 8;
const percentageValue = ((percentage / 100) * locationsItemWidth) + locationsItemWidth
locationsMore.style.width = percentageValue + 'px'
} else {
locationsMore.style.width = locationsItemWidth - 40 + 'px'
}
// get the height of each '.locations__more' element and then use the value to set the 'bottom' value of the '.locations__info' element
let locationsMoreHeight = locationsMore.offsetHeight
locationsInfo.style.bottom = '-' + locationsMoreHeight + 'px'
})
}
locationsHover('.locations-container--1')
locationsHover('.locations-container--2')
HTML
<section class="content width-medium | locations-container locations-container--1">
<div class="locations">
<div class="locations__item" data-type="{{ TYPE }}" data-id="{{ ID }}" tabindex="0">
<img src="https://images.unsplash.com/photo-1567745576351-5eb638a17443?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80" class="locations__image" loading="lazy" alt="{{ TITLE }}" width="825" height="460">
<div class="locations__info">
<h3 class="locations__title">Title of Thing</h3>
<address class="locations__address">Portland, OR</address>
<div class="locations__more">
<p class="locations__content">Lorem ipsum consectetur adipiscing elit. Suspendisse iaculis ultrices nisl, sit amet dictum ante pretium et. Nam ligula purus, accumsan nec lorem in, gravida lobortis nisi. Duis pulvinar cursus tellus at hendrerit. Morbi tempor sit amet libero non rutrum. Aenean vel risus lectus. Nunc in neque pulvinar, posuere diam faucibus, pretium arcu. Ut in sollicitudin eros. Phasellus congue risus felis, id mattis ante mollis non. Vivamus id pellentesque ex. Integer nec leo posuere, posuere felis eget, venenatis purus. Etiam gravida, felis vitae fermentum malesuada, nisi sem rhoncus erat, sed aliquet justo felis sit amet erat. Nulla vehicula facilisis ligula eu suscipit.</p>
<a href="https://www.alxwntr.com" target="_blank" rel="noreferrer" class="locations__link">Visit Website</a>
</div>
</div>
</div>
<div class="locations__item" data-type="{{ TYPE }}" data-id="{{ ID }}" tabindex="0">
<img src="https://images.unsplash.com/photo-1567745576352-e404ee640705?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1470&q=80" class="locations__image" loading="lazy" alt="{{ TITLE }}" width="825" height="460">
<div class="locations__info">
<h3 class="locations__title">Title of Thing</h3>
<address class="locations__address">Portland, OR</address>
<div class="locations__more">
<p class="locations__content">Lorem ipsum consectetur adipiscing elit. Suspendisse iaculis ultrices nisl, sit amet dictum ante pretium et. Nam ligula purus, accumsan nec lorem in, gravida lobortis nisi. Duis pulvinar cursus tellus at hendrerit. Morbi tempor sit amet libero non rutrum. Aenean vel risus lectus. Nunc in neque pulvinar, posuere diam faucibus, pretium arcu. Ut in sollicitudin eros. Phasellus congue risus felis, id mattis ante mollis non. Vivamus id pellentesque ex. Integer nec leo posuere, posuere felis eget, venenatis purus. Etiam gravida, felis vitae fermentum malesuada, nisi sem rhoncus erat, sed aliquet justo felis sit amet erat. Nulla vehicula facilisis ligula eu suscipit.</p>
<a href="https://www.alxwntr.com" target="_blank" rel="noreferrer" class="locations__link">Visit Website</a>
</div>
</div>
</div>
</div>
</div>
CSS
.locations {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1.5em;
}
.locations__item {
flex-grow: 0;
flex-shrink: 1;
flex-basis: calc(50% - 0.75em);
transition: all 1s;
position: relative;
isolation: isolate;
overflow: hidden;
&--expand {
flex-basis: calc(60% - 0.75em);
}
&--shrink {
flex-basis: calc(40% - 0.75em);
}
@media (max-width: 1024px) {
flex-basis: 100%;
}
&:before {
content: '';
background-color: rgb(0 0 0 / 0);
position: absolute;
inset: auto;
width: 100%;
height: 100%;
display: block;
transition: all 1s;
}
&:hover,
&:focus {
&:before {
background-color: rgb(0 0 0 / 0.75);
}
}
&:after {
content: '';
background: linear-gradient(0deg, rgba(0,0,0,1) 0%, rgba(255,255,255,0) 100%);
position: absolute;
inset: auto auto 0 auto;
width: 100%;
height: 50%;
display: block;
z-index: 1;
}
}
.locations__image {
display: block;
width: 100%;
height: 460px;
// max-height: 460px;
object-fit: cover;
}
.locations__info {
position: absolute;
inset: auto auto 0 clamp(1em, 5vw, 2em);
z-index: 2;
transition: all 1s;
pointer-events: none; // prevents bug that causes hover animation twitching in gaps
>* {
color: #fff;
}
}
.locations__title {
font-size: 1.35rem;
}
.locations__address {
font-size: 1rem;
text-transform: uppercase;
margin-bottom: 2em;
}
.locations__more {
color: #fff;
margin-top: 0;
opacity: 0;
transition: all 1s;
>* {
color: #fff;
}
}
.locations__content {
font-size: 1rem;
@media (max-width: 1024px) {
line-height: 1.25;
}
}
.locations__link {
font-size: 1rem;
text-transform: uppercase;
margin-bottom: 2em;
}
.locations__item {
outline: 0;
&:hover,
&:focus {
.locations__info {
bottom: 1em !important;
pointer-events: all;
}
.locations__more {
margin-top: -1.5em;
opacity: 1;
}
.locations__content {
}
}
}