웹사이트 검색

자바스크립트 - 서로 다른 시간에 시작/재시작된 CSS 애니메이션을 동기화하는 방법


저는 같은 애니메이션 클래스를 받는 여러 페이지 요소의 CSS 애니메이션을 동기화하려고 하고 있습니다.

이 주제에 대한 글이 많은 것 같지만 통일된 해결책은 없고, 제가 찾은 해결책 중 어느 것도 이 경우에 적용되는 것 같지 않습니다.

문제를 보여주기 위해 여기 jsfiddle을 준비했습니다:

https://jsfiddle.net/gdf7szo5/1/

어떤 글자를 클릭하면 해당 글자의 애니메이션이 시작됩니다. 다시 클릭하면 다른 애니메이션으로 전환되고, 세 번째로 클릭하면 글자에 애니메이션이 전혀 없도록 설정됩니다.

원하는 효과는 한 애니메이션에서 깜빡이는 모든 글자가 서로 동기화되는 것이고, 다른 애니메이션의 글자들도 서로 동기화되는 것입니다. 명확히 말하자면, 저는 두 애니메이션을 서로 동기화하려는 것이 아니라, 단지 주어진 애니메이션을 가진 모든 요소가 서로 동기화되기를 원합니다.

하지만 현재 한 글자가 애니메이션을 보여주고 다른 글자도 같은 애니메이션으로 설정하면, 타이밍이 완벽하지 않다면 두 글자의 애니메이션은 같은 애니메이션임에도 불구하고 동기화되지 않습니다.

다음은 사용 중인 코드입니다:

HTML :

<div>
  <span id="spA" onclick="toggleFx('spA')">A</span>
  <span id="spB" onclick="toggleFx('spB')">B</span>
  <span id="spC" onclick="toggleFx('spC')">C</span>
  <span id="spD" onclick="toggleFx('spD')">D</span>
</div>

CSS :

div {
  display: flex;
  flex-flow: column;
}

span {
  animation-name: pulse_active;
}

span.pulse {
    color: #f00;
    animation: pulse_active 1.5s ease-in infinite;
}

span.pulse_invert {
  color: #00f;
  animation: pulse_inverted 3s ease-in infinite;
}

@keyframes pulse_active {
    0% {
        opacity: 0;
    }
    50% {
        opacity: 0.66;
    }
    100% {
        opacity: 0;
    }
}

@keyframes pulse_inverted {
    0% {
        opacity: 1;
    }
    50% {
        opacity: 0.33;
    }
    100% {
        opacity: 1;
    }
}

JS :

let pulseNormal = {};
let pulseInvert = {};

function toggleFx(spanID) {

    let spanTarget = document.getElementById(spanID);
  
  // shift setting
  if (spanID in pulseInvert) {
  console.log('y');
  console.log(Object.keys(pulseInvert));
    delete pulseInvert[spanID]
  console.log(Object.keys(pulseInvert));
  } else if (spanID in pulseNormal) {
    pulseInvert[spanID] = true;
    delete pulseNormal[spanID];
  } else {
    pulseNormal[spanID] = true
  }
  
  // update display
  for (let sp of 'ABCD') {
    let span = document.getElementById(`sp${sp}`);
    
    // clear any prev animation
        span.classList.remove('pulse');     
    span.classList.remove('pulse_invert');
    
    // add/re-add animation as needed
    if (`sp${sp}` in pulseNormal) {             
        span.classList.add('pulse');
    } else if (`sp${sp}` in pulseInvert) {
        span.classList.add('pulse_invert');
    }
  }

}

답변 #1

이를 위해 document.getAnimations() 메서드는 유용합니다. 이 메서드는 현재 적용 중인 모든 애니메이션 객체의 배열을 반환하며, 그 대상 요소는 문서의 하위 요소입니다. 자세한 정보: https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations

document.getAnimations()의 항목에는 애니메이션 동기화에 사용할 수 있는 currentTime 속성이 포함됩니다. 자세한 정보: https://drafts.csswg.org/web-animations/#dom-documentorshadowroot-getanimations

toggleFx 함수가 수정되었으며 이제 세 부분으로 나뉩니다.

첫 번째 부분에서 document.getAnimations()를 검색하고, animationName이 pulse_active 또는 pulse_inverted 중 하나인 currentTime 속성을 찾으세요; 그런 다음 이를 관련 변수(예: pulseActiveStart 및 pulseInvertedStart)에 저장합니다.

두 번째 부분에서, spanTarget의 className이 당신이 말한 대로 변경되었습니다.

3부에서는 pulseActiveStart와 pulseInvertedStart가 관련 애니메이션의 currentTime 속성에 적용되었습니다(1부를 역순으로).

function toggleFx(spanID) {

    let spanTarget = document.getElementById(spanID);
    
    let  pulseActiveStart;
    let  pulseInvertedStart;
    let anims = document.getAnimations()    
    for(let i = 0; i < anims.length; i++) { 
        if(anims[i].animationName == "pulse_active") pulseActiveStart = anims[i].currentTime;
        else if(anims[i].animationName == "pulse_inverted") pulseInvertedStart = anims[i].currentTime;      
    }
    
    
    
    if(spanTarget.classList.contains('pulse_invert')) {
        spanTarget.classList.remove('pulse_invert');    
        spanTarget.classList.remove('pulse');   
    } else if(spanTarget.classList.contains('pulse')) {
        spanTarget.classList.add('pulse_invert');   
        spanTarget.classList.remove('pulse');   
    } else {
        spanTarget.classList.add('pulse');  
    }
    
    anims = document.getAnimations()    
    for(let i = 0; i < anims.length; i++) { 
        if(anims[i].animationName == "pulse_active") {
            if(pulseActiveStart) anims[i].currentTime = pulseActiveStart;           
        } else if(anims[i].animationName == "pulse_inverted") {
            if(pulseInvertedStart) anims[i].currentTime = pulseInvertedStart;       
        }
    }
}
div {
  display: flex;
  flex-flow: column;
}

span.pulse {
    color: #f00;
    animation: pulse_active 1.5s ease-in infinite;
}

span.pulse_invert {
  color: #00f;
  animation: pulse_inverted 3s ease-in infinite;
}

@keyframes pulse_active {
    0% { opacity: 0; }
    50% { opacity: 0.66; }
    100% {  opacity: 0; }
}

@keyframes pulse_inverted {
    0% { opacity: 1; }
    50% { opacity: 0.33; }
    100% {  opacity: 1; }
}
<div>
  <span id="spA" onclick="toggleFx('spA')">A</span>
  <span id="spB" onclick="toggleFx('spB')">B</span>
  <span id="spC" onclick="toggleFx('spC')">C</span>
  <span id="spD" onclick="toggleFx('spD')">D</span>
</div>

답변 #2

그들을 동기화하기 위해서는 애니메이션을 동시에 초기화해야 합니다.

여기서 나는 span에 대한 animation-name을 지정합니다. 다른 속성이 정의될 때까지 애니메이션은 실행되지 않습니다(사실 나는 다른 렌더링 엔진들이 동일한 동작을 공유하는지 궁금합니다).

let pulseList = {};

function toggleFx(spanID) {
  let spanTarget = document.getElementById(spanID);

  // toggle setting
  if (spanID in pulseList) {
    delete pulseList(spanID)
  } else {
    pulseList[spanID] = true;
  }

  // update display
  for (let sp of 'ABCD') {
    let span = document.getElementById(`sp${sp}`);
    span.classList.remove('pulse'); // clear any prev animation
    if (`sp${sp}` in pulseList) { // add/re-add animation as needed
      span.classList.add('pulse');
    }
  }
}
div {
  display: flex;
  flex-flow: column;
}

span {
  animation-name: pulse_active;
}

span.pulse {
  color: #f00;
  animation: pulse_active 1.5s ease-in infinite;
}

@keyframes pulse_active {
  0% {
    opacity: 0
  }
  50% {
    opacity: 0.66
  }
  100% {
    opacity: 0
  }
}
<div>
  <span id="spA" onclick="toggleFx('spA')">A</span>
  <span id="spB" onclick="toggleFx('spB')">B</span>
  <span id="spC" onclick="toggleFx('spC')">C</span>
  <span id="spD" onclick="toggleFx('spD')">D</span>
</div>

편집

적어도 Blink와 Gecko는 같은 동작을 공유하지만, 저는 WebKit Safari에서는 테스트하지 않았습니다.