Trigger the hover pseudo-class in mobile when the user scrolls over an element

Viewed 69

I have some cards which slide open when you hover your cursor over them. However, in mobile - I want the cards to slide open based on the user scrolling over them. What's a good, current way to do this? Is there a way to trigger pseudo-classes based on the relative position of an element in the mobile viewport?

Currently I'm just using :hover and translate to manipulate the sliding behaviour on desktop. Reproducible snippet below.

Note: The snippet will not display the exact styling/imagery as the cards on my website, but it is good enough for the purpose of figuring out a solution.

Your insights are appreciated!

David

.shadowEffect {
  box-shadow: 2px 6px 6px 0px #282e34;
}

.cardContainer {
  //padding: 0px 50px 0px 50px;
  //transform: translateY(8px);
  height: 100%;
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  //align-content: space-around;
}

.cardContainer .card {
  cursor: pointer;
  margin-top: 20px;
  margin-bottom: 20px;
  max-width: 300px;
  width: 270px;
}

.cardContainer .card .face {
  height: 200px;
  transition: 0.5s;
}
//Face1 is the panel which transitions towards the top, face2 is the pane which appears and transitions to the bottom
.cardContainer .card .face.face1 {
  position: relative;
  background: grey;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1;
  transform: translateY(100px);
}

.cardContainer .card:hover .face.face1 {
  background: white;
  transform: translateY(0);
}

.cardContainer .card .face.face1 .content {
  opacity: 0.8;
  transition: 0.5s;
}

.cardContainer .card:hover .face.face1 .content {
  opacity: 1;
}

//cardImage1 and cardImage2 are separated because the icons in the 2nd and 3rd card are smaller by scale
.cardImage1 {
  width: 45%;
}

.cardImage2 {
  width: 75%;
}

.content {
  display: flex;
  flex-direction: column;
  //justify-content: center;
  align-items: center;
}

.cardContainer .card .face.face1 .content h3 {
  margin: 10px 0 0;
  padding: 0;
  color: black;
  text-align: center;
}

.cardContainer .card .face.face2 {
  position: relative;
  background: #282e34;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
  box-sizing: border-box;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.8);
  transform: translateY(-100px);
}

.cardContainer .card:hover .face.face2 {
  transform: translateY(0);
}

.cardContainer .card .face.face2 .content p {
  margin: 0;
  padding: 0;
}

// End of card CSS
  <div class="cardContainer">
      <div class="card shadowEffect">
        <div class="face face1">
          <div class="content">
            <img class="cardImage1" src="./images/cardIcons/condo.svg">
            <h3 class="cardBanner">Real Estate</h3>
          </div>
        </div>
        <div class="face face2">
          <div class="content">
            <p>Every property. Same price.<br> 6 hour turnover.</p>
          </div>
        </div>
      </div>
      <div class="card shadowEffect">
        <div class="face face1">
          <div class="content">
            <img class="cardImage2" src="./images/cardIcons/marketingmedia.svg">
            <h3 class="cardBanner">Media</h3>
          </div>
        </div>
        <div class="face face2">
          <div class="content">
            <p>Videos. Photos. We're there.<br> Just give us a shot list.</p>
          </div>
        </div>
      </div>
      <div class="card shadowEffect">
        <div class="face face1">
          <div class="content">
            <img class="cardImage2" src="./images/cardIcons/training.svg">
            <h3 class="cardBanner">Training</h3>
          </div>
        </div>
        <div class="face face2">
          <div class="content">
            <p>Need experience flying? <br>We can help.</p>
          </div>
        </div>
      </div>
      <div class="card shadowEffect">
        <div class="face face1">
          <div class="content">
            <img class="cardImage2" src="./images/cardIcons/licensed.svg">
            <h3 class="cardBanner">Licensing</h3>
          </div>
        </div>
        <div class="face face2">
          <div class="content">
            <p>Need a drone license? <br>Message us today</p>
          </div>
        </div>
      </div>

    </div>

1 Answers

You can try using @media with (hover: hover) for when the device supports hover and (hover: none) for when it doesn't.

But I don't know a way through CSS to trigger a transition when the element is visible in the viewport. Therefore it is necessary to use JavaScript, through IntersectionObserver.

Update 1: I realized that just @media (hover: *) wasn't enough for some devices, so I also included (pointer: fine) along with (hover: hover) and (pointer: coarse) next to (hover: none).

Here's an example, just Toggle Device Toolbar (Ctrl + Shift + M in Chrome DevTools):

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    entry.target.classList.toggle("viewport-animation", entry.isIntersecting);
  });
});
document.querySelectorAll('.card').forEach(card => observer.observe(card, {}));
.shadowEffect {
  box-shadow: 2px 6px 6px 0px #282e34;
}

.cardContainer {
  //padding: 0px 50px 0px 50px;
  //transform: translateY(8px);
  height: 100%;
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  //align-content: space-around;
}

.cardContainer .card {
  cursor: pointer;
  margin-top: 20px;
  margin-bottom: 20px;
  max-width: 300px;
  width: 270px;
}

.cardContainer .card .face {
  height: 200px;
  transition: 0.5s;
}
//Face1 is the panel which transitions towards the top, face2 is the pane which appears and transitions to the bottom
.cardContainer .card .face.face1 {
  position: relative;
  background: grey;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1;
  transform: translateY(100px);
}

@media (hover: hover) and (pointer: fine) {
  .cardContainer .card:hover .face.face1 {
    background: white;
    transform: translateY(0);
  }
}
@media (hover: none), (pointer: coarse) {
  .cardContainer .card.viewport-animation .face.face1 {
    background: white;
    transform: translateY(0);
  }
}

.cardContainer .card .face.face1 .content {
  opacity: 0.8;
  transition: 0.5s;
}

@media (hover: hover) and (pointer: fine) {
  .cardContainer .card:hover .face.face1 .content {
    opacity: 1;
  }
}
@media (hover: none), (pointer: coarse) {
  .cardContainer .card.viewport-animation .face.face1 .content {
    opacity: 1;
  }
}

//cardImage1 and cardImage2 are separated because the icons in the 2nd and 3rd card are smaller by scale
.cardImage1 {
  width: 45%;
}

.cardImage2 {
  width: 75%;
}

.content {
  display: flex;
  flex-direction: column;
  //justify-content: center;
  align-items: center;
}

.cardContainer .card .face.face1 .content h3 {
  margin: 10px 0 0;
  padding: 0;
  color: black;
  text-align: center;
}

.cardContainer .card .face.face2 {
  position: relative;
  background: #282e34;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 20px;
  box-sizing: border-box;
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.8);
  transform: translateY(-100px);
}

@media (hover: hover) and (pointer: fine) {
  .cardContainer .card:hover .face.face2 {
    transform: translateY(0);
  }
}
@media (hover: none), (pointer: coarse) {
  .cardContainer .card.viewport-animation .face.face2 {
    transform: translateY(0);
  }
  .cardContainer .card.viewport-animation .face.face1 .content {
    opacity: 1;
  }
}

.cardContainer .card .face.face2 .content p {
  margin: 0;
  padding: 0;
}

// End of card CSS
<div class="cardContainer">
  <div class="card shadowEffect">
    <div class="face face1">
      <div class="content">
        <img class="cardImage1" src="./images/cardIcons/condo.svg">
        <h3 class="cardBanner">Real Estate</h3>
      </div>
    </div>
    <div class="face face2">
      <div class="content">
        <p>Every property. Same price.<br> 6 hour turnover.</p>
      </div>
    </div>
  </div>
  <div class="card shadowEffect">
    <div class="face face1">
      <div class="content">
        <img class="cardImage2" src="./images/cardIcons/marketingmedia.svg">
        <h3 class="cardBanner">Media</h3>
      </div>
    </div>
    <div class="face face2">
      <div class="content">
        <p>Videos. Photos. We're there.<br> Just give us a shot list.</p>
      </div>
    </div>
  </div>
  <div class="card shadowEffect">
    <div class="face face1">
      <div class="content">
        <img class="cardImage2" src="./images/cardIcons/training.svg">
        <h3 class="cardBanner">Training</h3>
      </div>
    </div>
    <div class="face face2">
      <div class="content">
        <p>Need experience flying? <br>We can help.</p>
      </div>
    </div>
  </div>
  <div class="card shadowEffect">
    <div class="face face1">
      <div class="content">
        <img class="cardImage2" src="./images/cardIcons/licensed.svg">
        <h3 class="cardBanner">Licensing</h3>
      </div>
    </div>
    <div class="face face2">
      <div class="content">
        <p>Need a drone license? <br>Message us today</p>
      </div>
    </div>
  </div>
</div>

Related