7

I set up a keyframe animation in CSS. Attached it to a DOM element and set it to pause. With javascript (jQuery), I am changing the animation delay from 0s to 100s achieving a nice animation while scrolling.

This works well on all of the browsers, but not on Safari (Version 11.1.1 (13605.2.8)).

$(document).ready(function() {
      fluider([
        {
          selector: '.manualAnim',
          start: 100,
          end: 500
        },

        {
          selector: '.manualAnim2',
          start: 500,
          end: 1000
        },

        {
          selector: '.manualAnim3',
          start: 0,
          end: 1500
        }

      ])
    })
    
    
    function fluider(o) {
      for(var i = 0; i < o.length; i++) {
        $(o[i].selector).css('animation-play-state','paused');
        $(o[i].selector).css('animation-duration','100s');
      }
      $(window).scroll(function() {
        var h = $(window).scrollTop();
        for(var i = 0; i < o.length; i++) {
    
            $(o[i].selector).css('animation-delay',-clamp(0,100,((h-o[i].start)/o[i].end * 100)) + 's');
        }
      });

    }
    
    function clamp(from, to, val) {
      if(val >= from) {
        if(val <= to) {
          return val;
        }
        else {
          return to;
        }
      }
        else {
          return from;
      }
    }
   body {
      height: 1000vh;
    }
    .manualAnim {
      position: fixed;
      display: block;
      width: 100px;
      height: 100px;
      background-color: red;
      animation: 100s anim paused both;
      animation-delay: 0s;
    }
    
    .manualAnim2 {
      position: fixed;
      display: block;
      left: 120px;
      width: 100px;
      height: 100px;
      background-color: red;
      animation: 100s anim paused both;
      animation-delay: 0s;
    }
    
    .manualAnim3 {
      position: fixed;
      display: block;
      left: 240px;
      width: 100px;
      height: 100px;
      background-color: red;
      animation: 100s anim paused both;
      animation-delay: 0s;
    }
    
    @keyframes anim{
      0% {
        background-color: red;
        transform: scale(1);
      }
      30% {
        background-color: green;
        transform: scale(1.5);
      }
      60% {
        background-color: blue;
        transform: scale(0.5);
      }
      100% {
        background-color: yellow;
        transform: scale(1);
      }
    }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="manualAnim"></div>
<div class="manualAnim2"></div>
<div class="manualAnim3"></div>

I Googled a few hours days for now, but I have no clue what could be the problem. Any idea?

Erik Putz
  • 329
  • 3
  • 4
  • 14

2 Answers2

1

You need to add the animation webkit to your code and css for Safari

-webkit-animation

-webkit-animation-delay

-webkit-animation-duration

-webkit-animation-play-state

@-webkit-keyframes

Stef
  • 87
  • 9
  • I already checked this solution, it does not help. I use autoprefixer on codepen, also setting the webkit version here as dom.style.webkitAnimationDelay see here: https://codepen.io/erikputz/pen/BPwdgN?editors=0111 – Erik Putz Jul 31 '18 at 07:44
1

After a lot of experimentation, here's a version with workarounds that gives smooth, expected behavior in both Safari 11.1.2 and Chrome 68 (and hopefully other browsers as well).

It looks like the underlying issue is that elements don't get redrawn when animation properties are changed for a paused animation, as the question states. This solution works around that by re-adding the necessary animation-related CSS (with the correct delay) each frame. This would normally cause flickering (since Safari tries to revert to the unanimated styling when the animation is removed), so this solution manually applies the current animation style each time it modifies the animation.

$(document).ready(function() {
  fluider([{
      selector: '.manualAnim',
      start: 100,
      end: 500
    },

    {
      selector: '.manualAnim2',
      start: 500,
      end: 1000
    },

    {
      selector: '.manualAnim3',
      start: 0,
      end: 1500
    }

  ])
})

function getAnimatedProperties(animName) {
  // Get an array of all property names that
  // are modified by the animation ${animName}
  let properties = {};
  let sheets = document.styleSheets;
  let propertyRegex = /([a-z\-]+):/ig;
  for (let sheet of sheets) {
    let rules = sheet.rules || sheet.cssRules;
    for (let r of rules) {
      if (r.name === animName) {
        let rText = r.cssText;
        let match = propertyRegex.exec(rText);
        while (match) {
          properties[match[1]] = true;
          match = propertyRegex.exec(rText);
        }
      }
    }
  }
  return Object.keys(properties);
}

function fluider(o) {
  const animationName = "anim";
  const preservedProperties = getAnimatedProperties(animationName);
  $(window).scroll(function() {
    var h = $(window).scrollTop();
    for (var i = 0; i < o.length; i++) {
      let el = document.querySelector(o[i].selector);
      let pct = 100 * (parseInt(h) - o[i].start) / o[i].end;
      let delay = -Math.max(Math.min(pct, 100), 0) + 's';
      let s = window.getComputedStyle(el);
      // without setting these properties and overwriting .style, 
      // the animation will flicker
      let preservedStyles = preservedProperties.map(p => `${p}: ${s[p]};`).join("");
      el.style = `${preservedStyles} animation-delay: ${delay}; animation-duration: 100s; animation-play-state: paused;`;
      // without scheduling this *using setTimeout*, 
      // the animation will not rerender
      window.setTimeout(() => {
        el.style.animationName = animationName;
      }, 0);
    }
  });
}
body {
  height: 1000vh;
}

.manualAnim {
  position: fixed;
  display: block;
  width: 100px;
  height: 100px;
  background-color: red;
}

.manualAnim2 {
  position: fixed;
  display: block;
  left: 120px;
  width: 100px;
  height: 100px;
  background-color: red;
}

.manualAnim3 {
  position: fixed;
  display: block;
  left: 240px;
  width: 100px;
  height: 100px;
  background-color: red;
}

@keyframes anim {
  0% {
    background-color: red;
    transform: scale(1);
  }
  30% {
    background-color: green;
    transform: scale(1.5);
  }
  60% {
    background-color: blue;
    transform: scale(0.5);
  }
  100% {
    background-color: yellow;
    transform: scale(1);
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="manualAnim"></div>
<div class="manualAnim2"></div>
<div class="manualAnim3"></div>
Ollin Boer Bohan
  • 2,296
  • 1
  • 8
  • 12
  • okay, it works for you. But i would like to make it universal, so I copied the calculated values to the current style: `dom.style=window.getComputedStyle(dom);` (dom is requested with get element by ID) . Afterwards I run the timeout similarly as your example: `window.setTimeout(() => { dom.style.animationName = window.getComputedStyle(dom).animationName; }, 0);`. However it does not work, anything I am missing? – Erik Putz Aug 02 '18 at 08:48
  • If you want to copy all styles, you can use this method https://stackoverflow.com/questions/19784064/set-javascript-computed-style-from-one-element-to-another, but that's too slow to work in an animation loop (it causes a lot of flickering/stuttering, since there are a lot of unused properties that get copied). I've modified my post to retrieve the animated properties directly from the stylesheet, which achieves the same result while setting the minimal number of properties. Also, re: `okay, it works for you.` Does it work for you? If not, which browser are you using? – Ollin Boer Bohan Aug 02 '18 at 14:49
  • Sorry, I did not meant it like that. The code works, I meant. But you are right, copying all of the properties is not a good idea, even the whole code seems slow by reading it. I will modify the input array, so the user will set, what animation properties are changed during the animation. To bad that safari does not work like chrome or firefox, this would be a neat trick. I'll try tomorrow and get back to you! – Erik Putz Aug 05 '18 at 19:36
  • Sounds good. The `getAnimatedProperties` function in the new version of the post should work to get a list of animated properties automatically from the animation name, so maybe that will be helpful? – Ollin Boer Bohan Aug 05 '18 at 22:20
  • Now, I got to the upgrade, it works like a charm. However still does not animate, but the properties got copied to the inline CSS. I added: `window.setTimeout(function() { dom.style.animationName = comStyle.animationName; }, 0);` as you suggested, but it still does not animate. See here: https://codepen.io/erikputz/pen/BPwdgN?editors=0111 Any idea what did I miss? – Erik Putz Aug 06 '18 at 07:49
  • A quick observation, when I see your script in action, the inline, animated values are changing. In mine are not. However I am doing the same thing - or not. No idea, why it behaves differently. – Erik Putz Aug 06 '18 at 11:24
  • The codepen you linked differs substantially from the script I provided, and introduces several bugs: • You set a value for `animation-name` in the CSS, so overwriting `.style` no longer clears `animation-name` (have to explicitly overwrite it). • You're reading the `animation-name` on every scroll event, which fails if the read occurs when the `animation-name` is cleared (should read only once). • You hoisted `dom` with `var` so all callbacks use the wrong `dom`. • You added a (slow) `console.log` between CSS writes, causing flickering. Fixed codepen: https://codepen.io/anon/pen/jpvbNX – Ollin Boer Bohan Aug 06 '18 at 15:16
  • (Hopefully the modified codepen helps! In general, if you have a working example of something, it's recommended to modify/test the working example incrementally to get your desired implementation, rather than rewriting it whole-cloth and looking for all the bugs at the end...) – Ollin Boer Bohan Aug 06 '18 at 17:49
  • 1
    I was avoiding let declaration on purpose, still have compatibility shivers. But if there is no other way, I bow to the new javascript. Thank you for your help, I will of course credit you on the documentation for this function. Also I owe you a beer :) – Erik Putz Aug 07 '18 at 06:48