Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

162
Views
Image won't stay visible for hover effect

Hello and thank you in advance for reading my question.

GOAL: Set image so that once it's scrolled into view it transitions smoothly into a set position - but still reacts to :hover. Using @keyframes and a little JavaScript, I set the image to opacity: 0 and it's final opacity to opacity: .85. Then I added a hover effect in CSS to make it's opacity: 1

The issue is once it's finished with it's transition - it disappears - reverting to it's original opacity which is zero. I managed to make it freeze at .85 with animation-fill-mode: forwards, rather than animation-fill-mode: none, but then it won't respond to :hover

And here's a test snippet of the problem in action:

let observer_img = new IntersectionObserver(updates => {
    updates.forEach(update => {
        if (update.isIntersecting) {
            update.target.classList.add('shift_frame_center_img');
        } else {
            update.target.classList.remove('shift_frame_center_img');
        }
    });
}, { threshold: 0 });

[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
  width: 100%;
  height: 100vh;
  background-color: lightgrey;
}

/* CHILD */
.features-img-wrapper img {
  width: 10rem;
  display: block;
  margin-left: auto;
  margin-right: auto;
  margin-top: 8rem;
  opacity: 0;
  transition: all .5s;
}

/* APPEND-CHILD */
.shift_frame_center_img {
  animation: center_img 1s 0.5s none;
}

/* CHILD ON HOVER */
.features-img-wrapper img:hover {
  opacity: 1;
  transform: scale(1.035);
}

/* KEYFRAMES */
@keyframes center_img {
  0% {
    transform: translateY(20rem);
  }
  100% {
    transform: translateY(0);
    opacity: .85;
  }
}
<body>
  <div class="features-img-wrapper">
      <img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
  </div>
</body>

If I could get a hand with this that would be wonderful, I'm a bit of a beginner and have already spent a few hours on this, all feedback welcome. Thank you very much.

about 4 years ago · Juan Pablo Isaza
2 answers
Answer question

0

Solution 1

To understand why the hover effect was not working with the animation-fill-mode: forwards, read this answer.

You can fix that by adding !important property to the hover styles:

.features-img-wrapper img:hover {
  opacity: 1 !important;
  transform: scale(1.035) !important;
}

The problem, in this case, is that the transition will not work for hover.

Solution 2

You could remove the animation entirely and add the final state styles to the shift_frame_center_img class.

But you would still need to use the !important property because of the CSS Specificity.

let observer_img = new IntersectionObserver(updates => {
    updates.forEach(update => {
        if (update.isIntersecting) {
            update.target.classList.add('shift_frame_center_img');
        } else {
            update.target.classList.remove('shift_frame_center_img');
        }
    });
}, { threshold: 0 });

[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
  width: 100%;
  height: 100vh;
  background-color: lightgrey;
}

/* CHILD */
.features-img-wrapper img {
  width: 10rem;
  transform: translateY(20rem);
  display: block;
  margin-left: auto;
  margin-right: auto;
  margin-top: 8rem;
  opacity: 0;
  transition: all .5s;
}

/* APPEND-CHILD */
.shift_frame_center_img {
  transform: none !important;
  opacity: .85 !important;
}

/* CHILD ON HOVER */
.features-img-wrapper img:hover {
  opacity: 1 !important;
  transform: scale(1.035) !important;
}
<body>
  <div class="features-img-wrapper">
    <img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
  </div>
</body>

about 4 years ago · Juan Pablo Isaza Report

0

This snippet removes the need for fill-mode forwards by setting the img to have opacity 1 as its initial state so it will revert to that at the end of the animation.

The animation itself is altered to take 1.5s rather than 1s with the first third simply setting the img opacity to 0 so it can't be seen. This gives the delay effect.

let observer_img = new IntersectionObserver(updates => {
    updates.forEach(update => {
        if (update.isIntersecting) {
            update.target.classList.add('shift_frame_center_img');
        } else {
            update.target.classList.remove('shift_frame_center_img');
        }
    });
}, { threshold: 0 });

[...document.querySelectorAll('.features-img-wrapper img')].forEach(element => observer_img.observe(element));
body {
  width: 100%;
  height: 100vh;
  background-color: lightgrey;
}

/* CHILD */
.features-img-wrapper img {
  width: 10rem;
  display: block;
  margin-left: auto;
  margin-right: auto;
  margin-top: 8rem;
  opacity: 0;
  transition: all .5s;
    opacity: 1;
}

/* APPEND-CHILD */
.features-img-wrapper img {
  animation: center_img 1.5s 0s none;
}

/* CHILD ON HOVER */
.shift_frame_center_img:hover {
  opacity: 1;
  transform: translateY(0) scale(1.035);
}

/* KEYFRAMES */
@keyframes center_img {
  0% {
    transform: translateY(20rem) scale(1);
    opacity: 0;
  }
    33.33% {
    transform: translateY(20rem) scale(1);
    opacity: 0;
  }
  100% {
    transform: translateY(0) scale(1);
    opacity: .85;
  }
}
<body>
  <div class="features-img-wrapper">
      <img src="https://synapse.it/wp-content/uploads/2020/12/test.png">
  </div>
</body>

Note: as each transform setting will reset anything that isn't included both tranlateY and scale are included in each setting.

Outside the SO snippet system it was possible to leave the animation settings untouched by chaining another animation to the front which ran for 0.5s and just set the img to opacity: 0. This did not work in the snippet system (it got into a loop of flashing on and off) hence the introduction of one but extended animation.

about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!