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

393
Views
CSS: How to don't reset onhover animation on every hover

I have a simple on-hover CSS animation which makes slide transition between images.

When the user makes the hover on SECTION ONE and before the animation ends make hover on SECTION two, animation restart and make lagging move.

MY CODE:

var $circle = $('#circle');

function moveCircle(e) {
    TweenLite.to($circle, 0.8, {
    css: {
      left: e.pageX,
      top: e.pageY
    }
  });
}

$(window).on('mousemove', moveCircle);
@import "compass/css3";

@keyframes in {
    from {
        transform: translateY(-100%);
    }
    to {
        transform: translateY(0);
    }
}
@keyframes out {
    from {
        transform: translateY(0);
    }
    to {
       transform: translateY(100%);
    }
}

html {
  background: #0E3741;
}

#circle {
  position: absolute;
  pointer-events : none;
  width: 400px;
  height: 200px;
  top: 50%;
  left: 50%;
  margin: -50px 0 0 -50px;
}

#circle .circle-wrapper {
    overflow: hidden;
    width: 400px;
    height: 200px;
    position: relative;
  }
  
#circle img {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    width: 400px;
    height: 200px;
    object-fit: cover;
    overflow: hidden;
  }

#wrapper {
  display: flex;
  flex-direction: column;
}

.special-element {
  width: 100%;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
}

#one {
  background: blue;
}

#two {
  background: red;
}

#one:hover ~ #circle .circle-wrapper #imgOne {
  animation: in 1s ease-in-out;
  z-index: 2;
}

#one:hover ~ #circle .circle-wrapper #imgTwo {
  animation: out 1s ease-in-out;
}

#two:hover ~ #circle .circle-wrapper #imgTwo {
  animation: in 1s ease-in-out;
  z-index: 2;
}

#two:hover ~ #circle .circle-wrapper #imgOne {
  animation: out 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/TweenMax.min.js"></script>

<section id="wrapper">
  <section class="special-element" id="one">
    section one
  </section>

  <section class="special-element" id="two">
    section two
  </section>
  
  <div id="circle">
    <div class="circle-wrapper">
      <img id="imgOne" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
    <img id="imgTwo" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
    </div>
  </div>
</section>

Is there any solution how I can prevent this lagging issue?

Maybe is there any solution to how I can solve it and make this animation smooth?

I'm looking for something like animation on this website.

over 4 years ago · Santiago Trujillo
4 answers
Answer question

0

I think that problem is because "moving circle function". Moving dom element with Left and right is not good for performance. You should move the circle with "transform". transform runs with GPU acceleration and it performs better and make move smooth.

Try this code.

function moveCircle(e) {
    TweenLite.to($circle, 0.8, {
    css: {
      transform: `translate(${e.pageX}px, ${e.pageY}px)`
    }
  });
}

over 4 years ago · Santiago Trujillo Report

0

Updated version

You can do a simplified version of it with gsap. It is probably best not to mix plain css too much with the gsap, unless you use css inside the gsap library. Because gsap will manipulate some of the props. E.g. the transformation. And it is better to use transform than just left/top because it is hardware accelerated.

I've done some improvements to the code I've posted before. It looks smoother now. In addition, I have added a little zoom and horizontal-shift effect - similar to the animation on the referenced website. Also, the animation now starts from the bottom.

The animation is really well done on the referenced page. It is done with WebGL. This is not your every-day animation and requires quite a bit of effort to make it work - at least for someone who is not a designer. It uses a 3d transformation matrix and some other effects together.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset-css@5.0.1/reset.min.css" />
  <script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
  <script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/plugins/CSSPlugin.min.js"></script>
  <style type="text/css">
    .section {
      display: block;
      width: 100%;
      height: 300px;
      border-bottom: 1px solid red;
    }
    
    .overlay {
      position: absolute;
      top: 0;
      left: 0;
      display: none;
      background: transparent;
      z-index: -1;
    }
    
    .stack {
      position: relative;
      min-width: 300px;
      min-height: 300px;
      width: 480px;
      height: 320px;
      max-width: 480px;
      max-height: 320px;
      overflow: hidden;
      z-index: 1;
    }
    
    .img {
      position: absolute;
      top: 0;
      left: 0;
      width: auto;
      height: auto;
      max-width: 100%;
      object-fit: contain;
      z-index: -1;
    }
  </style>
</head>

<body>
  <main class="main">
    <section class="section section-1" data-img="img-1">section 1</section>
    <section class="section section-2" data-img="img-2">section 2</section>
    <div class="overlay">
      <div class="stack">
        <img id="img-1" class="img" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
        <img id="img-2" class="img" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
      </div>
    </div>
  </main>

  <script type="application/javascript">
    window.onload = () => {
      const overlay = document.querySelector(".overlay");
      const stack = document.querySelector(".stack");
      const s1 = document.querySelector(".section-1");
      const s2 = document.querySelector(".section-2");
      const main = document.querySelector(".main");

      const overlaySize = {
        width: 480,
        height: 320
      };
      const easeFunc = "sine.inOut";
      const easeDuration = 0.5;

      let animation;
      let activeSection;
      let currentTarget;

      function createAnimation() {
        //console.log('create animation');
        t1 = gsap.timeline({
          paused: true
        });
        t1.to(currentTarget, {
          zIndex: 2,
          display: "block"
        }, 0);
        t1.fromTo(currentTarget, {
          y: "100%"
        }, {
          y: 0,
          duration: easeDuration,
          ease: easeFunc
        }, 0);
        t1.to(currentTarget, {
          scale: 1.25,
          transformOrigin: "center",
          duration: easeDuration,
          ease: easeFunc
        }, 0);
        stack.querySelectorAll(".img").forEach((it) => {
          if (it !== currentTarget) {
            t1.to(it, {
              zIndex: -1
            }, 0);
            t1.to(it, {
              scale: 1,
              transformOrigin: "center"
            }, 0);
            t1.to(it, {
              display: "none"
            }, easeDuration);
          }
        });
        return t1;
      }

      function onMouseLeave(e) {
        const target = e.target;
        //console.log("leave", e.target);
        if (target === activeSection) {
          gsap.set(overlay, {
            display: "none"
          });
          currentTarget = null;
        }
      }

      function onMouseEnter(e) {
        currentTarget = stack.querySelector(`#${e.target.dataset.img}`);
        gsap.set(overlay, {
          display: "block"
        });
        if (!animation) {
          //console.log("undefined animation")
          animation = createAnimation();
          animation.play();
        } else if (animation.isActive()) {
          //console.log("still active");
          animation.timeScale(10); // fast forward the rest of the animation
          animation = createAnimation();
          animation.timeScale(1).play();
        } else {
          //console.log("no longer active");
          animation = createAnimation();
          animation.play();
        }
      }

      function onMouseMove(e) {
        const hoveredEl = document.elementFromPoint(e.pageX, e.pageY);
        if (hoveredEl.classList.contains("section")) {
          if (activeSection !== hoveredEl) {
            activeSection = hoveredEl;
          }
        } else if (hoveredEl.classList.contains("overlay") || hoveredEl.classList.contains("stack") || hoveredEl.classList.contains("pointer")) {
          // do nothing
        } else {
          if (activeSection) {
            activeSection = null;
          }
        }

        if (currentTarget) {
          // update overlay
          gsap.set(overlay, {
            x: e.pageX - overlaySize.width / 2,
            y: e.pageY - overlaySize.height / 2
          });

          // add a little horizontal-shift effect
          const dx = window.innerWidth / 2 - e.pageX;
          const offsetX = dx / window.innerWidth / 2 * 100;
          gsap.to(currentTarget, {
            x: offsetX * 2,
            duration: 2
          }, 0);
        }
      }

      gsap.set(overlay, {
        x: 0,
        y: 0
      });
      stack.querySelectorAll('.img').forEach((it) => gsap.set(it, {
        x: 0,
        y: "100%"
      }));

      window.addEventListener("mousemove", onMouseMove);
      s1.addEventListener("mouseleave", onMouseLeave);
      s2.addEventListener("mouseleave", onMouseLeave);
      s1.addEventListener("mouseenter", onMouseEnter);
      s2.addEventListener("mouseenter", onMouseEnter);
    }
  </script>
</body>

</html>

Old answer

I have been playing around a little bit with the gsap library today. I've honestly never done anything with or like it. Tried to do it with the x and y params that you may pass to gsap. It will take care of the transformations - also the TimeLine stuff
is quite handy. The result is not that great, also the animations look like it could be done better, but maybe it might still help you out. You could also improve some of the logic and animation probably. At least it runs quite stable - performance wise.

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset-css@5.0.1/reset.min.css" />
  <script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
  <script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/plugins/CSSPlugin.min.js"></script>
  <style type="text/css">
    .section {
      display: block;
      width: 100%;
      height: 200px;
      border-bottom: 1px solid red;
    }
    
    .overlay {
      position: absolute;
      top: 0;
      left: 0;
      display: none;
      border: none; // 1px dashed black;
      background: transparent; // lavender;
      overflow: hidden;
    }
    
    .stack {
      position: relative;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      min-width: 300px;
      min-height: 300px;
      width: 480px;
      height: 320px;
      z-index: 0;
    }
    
    .anim-img {
      position: absolute;
      top: 0;
      left: 0;
      width: auto;
      height: auto;
      max-width: 100%;
      object-fit: contain;
      z-index: 1;
    }
  </style>
</head>

<body>
  <main class="main">
    <section class="section section-1">section 1</section>
    <section class="section section-2">section 2</section>
    <div class="overlay">
      <div class="stack">
        <img id="img-1" class="anim-img" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
        <img id="img-2" class="anim-img" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
      </div>
    </div>
  </main>

  <script type="application/javascript">
    window.onload = () => {
      const overlay = document.querySelector(".overlay");
      const img1 = document.getElementById("img-1");
      const img2 = document.getElementById("img-2");
      const s1 = document.querySelector(".section-1");
      const s2 = document.querySelector(".section-2");
      const main = document.querySelector(".main");

      let anim;

      let isS1active = false;
      let isS2active = false;

      let showEl;
      let hideEl;

      let leaveTimeout;

      function reverseFadeInOut(showEl, hideEl) {
        console.log("create reverse timeline anim -> ", {
          showEl,
          hideEl
        });
        const tl = gsap.timeline({
          paused: true
        });
        tl
          .to(showEl, {
            zIndex: 1
          }, 0)
          .to(hideEl, {
            zIndex: 10
          }, 0)
          .to(hideEl, {
            y: "-100%",
            duration: 0.375
          }, 0)
          .to(hideEl, {
            display: "none"
          }, 0.375)
          .to(hideEl, {
            zIndex: 1
          }, 0.375)
          .to(showEl, {
            display: "block",
            zIndex: 10
          }, 0.375)
          .fromTo(showEl, {
            y: "-100%"
          }, {
            y: 0,
            duration: .375
          }, 0.375)
          .to(hideEl, {
            display: "none"
          });
        return tl;
      }

      function fadeInOut(showEl, hideEl) {
        console.log("create timeline anim -> ", {
          showEl,
          hideEl
        });
        const tl = gsap.timeline({
          paused: true
        });
        tl
          .to(hideEl, {
            zIndex: 1
          }, 0)
          .to(showEl, {
            display: "block",
            zIndex: 10
          }, 0)
          .fromTo(showEl, {
            y: "-100%"
          }, {
            y: 0,
            duration: .75
          }, 0)
          .fromTo(hideEl, {
            y: 0
          }, {
            y: "-100%",
            duration: .75
          }, 0)
          .to(hideEl, {
            display: "none"
          }, 0.75);
        return tl;
      }

      function animateImage() {
        if (isS1active || isS2active) {
          if (isS1active) {
            showEl = img1;
            hideEl = img2;
          } else if (isS2active) {
            showEl = img2;
            hideEl = img1;
          }

          if (!anim) {
            console.log("create new animation");
            anim = fadeInOut(showEl, hideEl);
            anim.play();
          } else {
            console.log("anim active:", anim.isActive());
            if (anim.isActive()) {
              console.log("reverse");
              anim.kill();
              anim = reverseFadeInOut(showEl, hideEl);
              anim.play();
            } else {
              anim = fadeInOut(showEl, hideEl);
              anim.play();
            }
          }
        }
      }

      function moveOverlay(e) {
        e.preventDefault();
        e.stopPropagation();
        gsap.set(overlay, {
          x: e.pageX + 15,
          y: e.pageY + 15,
          display: isS1active || isS2active ? "block" : "none"
        });
      }

      function mouseOver(e, el, isEntering) {
        e.preventDefault();
        e.stopPropagation();
        el.classList.toggle("active");
        isS1active = s1.classList.contains("active");
        isS2active = s2.classList.contains("active");
        if (isEntering) {
          clearTimeout(leaveTimeout);
          animateImage();
        } else {
          leaveTimeout = setTimeout(() => {
            if (anim) {
              console.log("kill anim");
              anim.kill();
              anim = null;
            }
            gsap.timeline({
              onComplete: () => {
                console.log("clear props");
                gsap.set(".anim-img", {
                  clearProps: true
                });
              }
            });
          }, 500);
        }
      }

      gsap.set(overlay, {
        x: "0",
        y: "0"
      });
      gsap.set(img1, {
        x: "0",
        y: "-100%"
      });
      gsap.set(img2, {
        x: "0",
        y: "-100%"
      });
      window.addEventListener("mousemove", moveOverlay);
      s1.addEventListener("mouseenter", (e) => {
        mouseOver(e, s1, true);
      });
      s1.addEventListener("mouseleave", (e) => {
        mouseOver(e, s1, false);
      });
      s2.addEventListener("mouseenter", (e) => {
        mouseOver(e, s2, true);
      });
      s2.addEventListener("mouseleave", (e) => {
        mouseOver(e, s2, false);
      });
    }
  </script>
</body>

</html>

over 4 years ago · Santiago Trujillo Report

0

Yes, there is, by modifying the value of the second parameter of TweenLite.to, because that's the duration, see more here: http://www.tud.ttu.ee/im/Jaak.Henno/FlashDevelop/greensock-as3/greensock-as3/docs/com/greensock/TweenLite.html#to()

enter image description here

var $circle = $('#circle');

function moveCircle(e) {
    TweenLite.to($circle, 0.1, {
    css: {
      left: e.pageX,
      top: e.pageY
    }
  });
}

$(window).on('mousemove', moveCircle);
@import "compass/css3";

@keyframes in {
    from {
        transform: translateY(-100%);
    }
    to {
        transform: translateY(0);
    }
}
@keyframes out {
    from {
        transform: translateY(0);
    }
    to {
       transform: translateY(100%);
    }
}

html {
  background: #0E3741;
}

#circle {
  position: absolute;
  pointer-events : none;
  width: 400px;
  height: 200px;
  top: 50%;
  left: 50%;
  margin: -50px 0 0 -50px;
}

#circle .circle-wrapper {
    overflow: hidden;
    width: 400px;
    height: 200px;
    position: relative;
  }
  
#circle img {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    width: 400px;
    height: 200px;
    object-fit: cover;
    overflow: hidden;
  }

#wrapper {
  display: flex;
  flex-direction: column;
}

.special-element {
  width: 100%;
  height: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
}

#one {
  background: blue;
}

#two {
  background: red;
}

#one:hover ~ #circle .circle-wrapper #imgOne {
  animation: in 1s ease-in-out;
  z-index: 2;
}

#one:hover ~ #circle .circle-wrapper #imgTwo {
  animation: out 1s ease-in-out;
}

#two:hover ~ #circle .circle-wrapper #imgTwo {
  animation: in 1s ease-in-out;
  z-index: 2;
}

#two:hover ~ #circle .circle-wrapper #imgOne {
  animation: out 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/TweenMax.min.js"></script>

<section id="wrapper">
  <section class="special-element" id="one">
    section one
  </section>

  <section class="special-element" id="two">
    section two
  </section>
  
  <div id="circle">
    <div class="circle-wrapper">
      <img id="imgOne" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
    <img id="imgTwo" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
    </div>
  </div>
</section>

over 4 years ago · Santiago Trujillo Report

0

The website is using canvas for the moving slideshow and they've used webgl for the smooth kind of reveal effects.

<div class="site-footer">
 <div class="js-scroll-height"></div>
 <canvas width="780" height="624" class="js-webgl" style="width: 1041px;height: 833px;opacity: 1;border: 1px solid brown;"></canvas>
</div>


We can create similar reveal effect using simple animation and z-index manipulation:

var $cursor = $('#cursor');

function movecursor(e) {
  TweenLite.to($cursor, 0.8, {
    css: {
      left: e.pageX,
      top: e.pageY
    }
  });
}
$(window).on('mousemove', movecursor);

let topImg = null;
$('.special-element').mouseenter((e) => {
  //make all images at same level
  $('.img-wrapper').css({ zIndex: '1' });

  //make last focused image at second highest level
  if (topImg) topImg.css({ zIndex: '2' });

  //make current image as topImage 
  let atr = $(e.target).attr('data-img');
  topImg = $('.img-wrapper[data-img="' + atr + '"]');
  //make it topmost
  topImg.css({ zIndex: '3' });
});
:root {
  --cursor-img-height: 30vh;
  --cursor-img-width: 30vw;
}

html,
body {
  background: #0E3741;
}

#wrapper {
  display: flex;
  flex-direction: column;
}

.special-element {
  width: 100%;
  height: 33vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 2rem;
}

.special-element:nth-child(1) {
  background: lightblue;
}

.special-element:nth-child(2) {
  background: lightcoral;
}

.special-element:nth-child(3) {
  background: lightgreen;
}

#cursor {
  position: absolute;
  pointer-events: none;
  margin: 0;
  margin-top: calc(var(--cursor-img-height) * -0.5);
  margin-left: calc(var(--cursor-img-width) * -0.5);
  overflow: hidden;
  width: var(--cursor-img-width);
  height: var(--cursor-img-height);
}

.img-wrapper {
  position: absolute;
  bottom: 0;
  left: 0;
  width: var(--cursor-img-width);
  height: var(--cursor-img-height);
  overflow: hidden;
}

.img-wrapper>img {
  position: absolute;
  bottom: 0;
  left: 0;
  width: var(--cursor-img-width);
  height: var(--cursor-img-height);
  object-fit: fill;
  z-index: 1;
}

.special-element[data-img="one"]:hover~#cursor [data-img="one"],
.special-element[data-img="two"]:hover~#cursor [data-img="two"],
.special-element[data-img="three"]:hover~#cursor [data-img="three"] {
  animation: slide .8s ease-in-out;
}

@keyframes slide {
  from {
    height: 0px;
    transform: scale(1.2);
  }
  to {
    height: (--cursor-img-height);
    transform: scale(1);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/TweenMax.min.js"></script>

<section id="wrapper">
  <section class="special-element" data-img="one">food</section>
  <section class="special-element" data-img="two">animal</section>
  <section class="special-element" data-img="three">night</section>

  <div id="cursor">
    <div class="img-wrapper" data-img='one'>
      <img src="https://picsum.photos/id/674/400/200">
    </div>
    <div class="img-wrapper" data-img='two'>
      <img src="https://picsum.photos/id/433/400/200">
    </div>
    <div class="img-wrapper" data-img='three'>
      <img src="https://picsum.photos/id/901/400/200">
    </div>
  </div>
</section>


View in full page mode.

over 4 years ago · Santiago Trujillo 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!