Search code examples
jquerycsssafaricss-animationscss-variables

Using jQuery to set CSS Variable for Keyframe Animation - Non-functional in Safari and Mobile


I have a CSS keyframe animation that uses a CSS variable as one of the animation properties. This variable is defined in jQuery. The entire animation seems to work fine in Chrome and Firefox. However, in Safari and on mobile browsers, only the portions of the animation not using the variable work correctly.

I've spent a lot of time trying to debug the issue...

  • Testing on the most recent versions of all browsers.
  • Checking my Developer tools on the page in Safari shows the variable as being defined correctly (e.g. 1200px).
  • I've hard-coded the variable as that same pixel number in the CSS and the animation works as expected.
  • I've hard-coded the variable as that same pixel number in jQuery (rather than having it calculated) and the animation doesn't work.
  • I've used the variable as a property in the element selector itself (rather than the keyframe) and it positions the element as expected.
  • Tried using "transform: translateY(...)" instead of "top:" and the results are the same.
  • Every so often, if I leave the window open, the animation will "kick in" and start functioning as expected. However, I can't manually replicate this. There's no consistency in it from what I can tell.

From all this, it seems to be a problem with the combination of the variable being defined in jQuery (also didn't work for straight JavaScript) and being used in the animation.

Any thoughts on what's going on?

function refHeight() {
  if ($('#main-content').length != 0) {
    $(':root').css('--varheight', Math.round($('#main-content').outerHeight()) + 'px');
  }
}
window.addEventListener('load', refHeight);
window.addEventListener('resize', refHeight);
:root {
  --hardvarheight: 75vh;
}

#main-content {
  width: 100%;
  height: 75vh;
}

.item {
  width: 75px;
  height: 50px;
  font-size: 80%;
}

.one {
  animation: animOne 3s linear infinite alternate;
  position: absolute;
  background-color: pink;
}

.two {
  position: absolute;
  top: calc(var(--varheight) / 100 * 50);
  left: 50vw;
  background-color: lightblue;
}

.three {
  animation: animThree 3s linear infinite alternate;
  position: absolute;
  background-color: lightgreen;
}

@keyframes animOne {
  0% {top: calc(var(--varheight) / 100 * 10); left: 40vw;}
  50% {top: calc(var(--varheight) / 100 * 20); left: 50vw;}
  100% {top: calc(var(--varheight) / 100 * 10);left: 60vw;}
}

@keyframes animThree {
  0% {top: calc(var(--hardvarheight) / 100 * 80); left: 40vw;}
  50% {top: calc(var(--hardvarheight) / 100 * 90); left: 50vw;}
  100% {top: calc(var(--hardvarheight) / 100 * 80); left: 60vw;}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="main-content">
  <div class="item one">Animated Using jQuery Variable</div>
  <div class="item two">Positioned Using jQuery Variable</div>
  <div class="item three">Animated Using CSS Variable</div>
</div>


Solution

  • I think I've finally figured it out. Safari doesn't seem to like to recalculate any CSS calc() values after they've been initially defined.

    Instead of having the animation property attached to the element in CSS, I created a separate class and added addClass() to the element after the variable was calculated. Seems to work.

    The only bug I'm having to sort out now is how to get it to recalculate the calc() value in the keyframe animation once the browser is resized.

    function refHeight() {
      if ($('#main-content').length != 0) {
        $(':root').css('--varheight', Math.round($('#main-content').outerHeight()) + 'px');
        $('.one').addClass('animated');
      }
    }
    window.addEventListener('load', refHeight);
    window.addEventListener('resize', refHeight);
    :root {
      --hardvarheight: 75vh;
    }
    
    #main-content {
      width: 100%;
      height: 75vh;
    }
    
    .item {
      width: 75px;
      height: 50px;
      font-size: 80%;
    }
    
    .one {
      position: absolute;
      background-color: pink;
    }
    
    .two {
      position: absolute;
      top: calc(var(--varheight) / 100 * 50);
      left: 50vw;
      background-color: lightblue;
    }
    
    .three {
      animation: animThree 3s linear infinite alternate;
      position: absolute;
      background-color: lightgreen;
    }
    .animated {
      animation: animOne 3s linear infinite alternate
    }
    @keyframes animOne {
      0% {top: calc(var(--varheight) / 100 * 10); left: 40vw;}
      50% {top: calc(var(--varheight) / 100 * 20); left: 50vw;}
      100% {top: calc(var(--varheight) / 100 * 10);left: 60vw;}
    }
    
    @keyframes animThree {
      0% {top: calc(var(--hardvarheight) / 100 * 80); left: 40vw;}
      50% {top: calc(var(--hardvarheight) / 100 * 90); left: 50vw;}
      100% {top: calc(var(--hardvarheight) / 100 * 80); left: 60vw;}
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="main-content">
      <div class="item one">Animated Using jQuery Variable</div>
      <div class="item two">Positioned Using jQuery Variable</div>
      <div class="item three">Animated Using CSS Variable</div>
    </div>