How to restart updated animation after dynamically changing keyframes?

时间:2018-05-28 18:51:02

标签: javascript jquery css typescript animation

I'm trying to create a marquee (yes, I've done LOTS of searching on that topic first) using animated text-indent. I prefer this solution over others I've tried, like using translation 100%, which causes text to leak out beyond the boundaries of my marquee.

I've been trying to follow this example here: https://www.jonathan-petitcolas.com/2013/05/06/simulate-marquee-tag-in-css-and-javascript.html

...which I've updated a bit, doing it in TypeScript, using API updates (appendRule instead of insertRule) and dropping concerns about old browser support.

The problem is that the animation restarts using the old keyframe rules -- the step described by the comment "re-assign the animation (to make it run)" doesn't work.

I've looked at what's going on in a debugger, and the rules are definitely being changed -- old rules deleted, new rules added. But it's as if the old rules are cached somewhere, and they aren't being cleared out.

Here's my current CSS:

#marquee {
  position: fixed;
  left: 0;
  right: 170px;
  bottom: 0;
  background-color: midnightblue;
  font-size: 14px;
  padding: 2px 1em;
  overflow: hidden;
  white-space: nowrap;
  animation: none;
}

#marquee:hover {
  animation-play-state: paused;
}

@keyframes marquee-0 {
  0% {
    text-indent: 450px;
  }

  100% {
    text-indent: -500px;
  }
}

And the relevant section of my TypeScript:

function updateMarqueeAnimation() {
  const marqueeRule = getKeyframesRule('marquee-0');

  if (!marqueeRule)
    return;

  marquee.css('animation', 'unset');

  const element = marquee[0];
  const textWidth = getTextWidth(marquee.text(), element);
  const padding = Number(window.getComputedStyle(element).getPropertyValue('padding-left').replace('px', '')) +
                  Number(window.getComputedStyle(element).getPropertyValue('padding-right').replace('px', ''));
  const offsetWidth = element.offsetWidth;

  if (textWidth + padding <= offsetWidth)
    return;

  marqueeRule.deleteRule('0%');
  marqueeRule.deleteRule('100%');
  marqueeRule.appendRule('0% { text-indent: ' + offsetWidth + 'px; }');
  marqueeRule.appendRule('100% { text-indent: -' + textWidth + 'px; }');

  setTimeout(() => marquee.css('animation', 'marquee-0 15s linear infinite'));
}

I've tried a number of tricks so far to get around this problem, including things like cloning the marquee element and replacing it with its own clone, and none of that has helped -- the animation continues to run as if the original stylesheet values are in effect, so the scrolling of the marquee doesn't adapt to different widths of text.

The next thing I'll probably try is dynamically creating new keyframes objects instead of editing the rules inside of an existing keyframes object, but that's a messy solution I'd rather avoid if anyone has a better solution.

1 个答案:

答案 0 :(得分:0)

我找到了一种让我的选框工作的方法,它确实涉及从样式表中动态添加和删除关键帧规则,但这并不像我想象的那样痛苦或丑陋。

let animationStyleSheet: CSSStyleSheet;
let keyframesIndex = 0;
let lastMarqueeText = '';

function updateMarqueeAnimation(event?: Event) {
  const newText = marquee.text();

  if (event === null && lastMarqueeText === newText)
    return;

  lastMarqueeText = newText;
  marquee.css('animation', 'none');

  const element = marquee[0];
  const textWidth = getTextWidth(newText, element);
  const padding = Number(window.getComputedStyle(element).getPropertyValue('padding-left').replace('px', '')) +
                  Number(window.getComputedStyle(element).getPropertyValue('padding-right').replace('px', ''));
  const offsetWidth = element.offsetWidth;

  if (textWidth + padding <= offsetWidth)
    return;

  if (!animationStyleSheet) {
    $('head').append('<style id="marquee-animations" type="text/css"></style>');
    animationStyleSheet = ($('#marquee-animations').get(0) as HTMLStyleElement).sheet as CSSStyleSheet;
  }

  if (animationStyleSheet.cssRules.length > 0)
    animationStyleSheet.deleteRule(0);

  const keyframesName = 'marquee-' + keyframesIndex++;
  const keyframesRule = `@keyframes ${keyframesName} { 0% { text-indent: ${offsetWidth}px } 100% { text-indent: -${textWidth}px; } }`;
  const seconds = (textWidth + offsetWidth) / 100;

  animationStyleSheet.insertRule(keyframesRule, 0);
  marquee.css('animation', `${keyframesName} ${seconds}s linear infinite`);
}

这里还有其他一些不需要通用解决方案的东西。有一件事是调用此方法有两个原因:正在调整窗口大小,或者已对字幕文本进行更新。我总是希望在调整窗口大小时更新,但如果文本没有更改,我不想更新动画,否则当有人试图读取它时,它可能会不必要地重置。

另一件事是,如果没有滚动它恰好适合它,我根本不想要文本滚动。