无限 SVG 动画在悬停时平滑地检索初始状态

Posted

技术标签:

【中文标题】无限 SVG 动画在悬停时平滑地检索初始状态【英文标题】:Infinite SVG animation smoothly retrieve initial state on hover 【发布时间】:2021-03-09 07:33:36 【问题描述】:

我希望我的无限 svg 动画在悬停时暂停在其初始或最终状态。我尝试使用 JS 删除类或在 CSS 中将animation-iteration-count: unset; 设置为悬停或设置animation: none;,但在每种情况下,转换都是突然的。我希望它顺利。这是代码: html

    <div class="button">
        <svg version="1.1" id="sound--icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
        viewBox="0 0 100 100"   >
          <rect class="rect-1 rect-anim" x="10" fill="currentColor"></rect>
          <rect class="rect-2 rect-anim" x="30" fill="currentColor"></rect>
          <rect class="rect-3 rect-anim" x="50" fill="currentColor"></rect> 
          <rect class="rect-4 rect-anim" x="70" fill="currentColor"></rect>
      </svg>
    </div>

CSS

#sound--icon .rect-anim
    animation-name: sound-icon-anim;
    animation-duration: .8s;
    animation-timing-function: cubic-bezier(.97,0,0,1);
    animation-iteration-count: infinite;


/*  #sound--icon:hover .rect-anim
    animation-iteration-count: unset;
 
 */

#sound--icon .rect-1
    animation-delay: .1s;


#sound--icon .rect-2
    animation-delay: .3s;


#sound--icon .rect-3
    animation-delay: .5s;


#sound--icon .rect-4
    animation-delay: .7s;


@keyframes sound-icon-anim
    0%
        height: 30%;
    
    10%
        height: 70%;
    
    20%
        height: 50%;
    
    50%
        height: 80%;
    
    60%
        height: 100%;
    
    70%
        height: 70%;
    
    100%
        height: 30%;
    

我最终的目标是:

动画正在运行 当 div 悬停/动画暂停时,高度平滑地达到 50% 当点击 div/动画暂停时,高度平滑地达到 30% 当 div 再次悬停/动画仍然暂停时,高度平滑地达到 50% 再次单击 div 时动画会重新运行(因此从 50% 开始,而不是从 0% 动画状态状态的 30% 开始)

在纯 CSS 中这对您来说是否可行?

【问题讨论】:

【参考方案1】:

我认为您无法在纯 CSS 中做到这一点。使用Web Animations API,这是可能的,但在当前的实施状态下,只有部分方法是现实的。

您可以实现的是在迭代周期结束时停止动画。在一个不是循环结束的点停止将需要实现AnimationTimeline。但目前 Chrome 缺少它。

document.querySelectorAll('.button').forEach(button => 
    //.getAnimations() returns an array
    const animations = [...button.querySelectorAll('.rect-anim')]
        .map(r => r.getAnimations())
        .flat();

    button.addEventListener('mouseenter', () => 
        for (let animation of animations) 
            const iterations = animation.effect.getComputedTiming()
                .currentIteration + 1;
            animation.effect.updateTiming(iterations)
        
    );

    button.addEventListener('mouseleave', () => 
        for (let animation of animations) 
            animation.effect.updateTiming(iterations: Infinity);
        
  );
);
.button 
  margin: 20px;
  width: 100px;


#sound--icon .rect-anim
    animation-name: sound-icon-anim;
    animation-duration: .8s;
    animation-timing-function: cubic-bezier(.97,0,0,1);
    animation-iteration-count: infinite;


#sound--icon .rect-1
    animation-delay: .1s;


#sound--icon .rect-2
    animation-delay: .3s;


#sound--icon .rect-3
    animation-delay: .5s;


#sound--icon .rect-4
    animation-delay: .7s;


@keyframes sound-icon-anim
    0%
        height: 30%;
    
    10%
        height: 70%;
    
    20%
        height: 50%;
    
    50%
        height: 80%;
    
    60%
        height: 100%;
    
    70%
        height: 70%;
    
    100%
        height: 30%;
    
<div id="this-button" class="button">
    <svg version="1.1" id="sound--icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
    viewBox="0 0 100 100"   >
      <rect class="rect-1 rect-anim" x="10" y="40"   fill="currentColor"></rect>
      <rect class="rect-2 rect-anim" x="30" y="40"   fill="currentColor"></rect>
      <rect class="rect-3 rect-anim" x="50" y="40"   fill="currentColor"></rect> 
      <rect class="rect-4 rect-anim" x="70" y="40"   fill="currentColor"></rect>
  </svg>
</div>

【讨论】:

【参考方案2】:

谢谢老兄,我学到了一些东西。最后,我对 JS 中的结果做了一些非常令人满意的事情。当悬停getComputedStyle 时,我计算了矩形的高度,并将其更改为我想要添加过渡的高度。我认为代码可以优化,尤其是setTimeout 部分,它删除了定义动画的 CSS 类。如果它不存在,它会与第四个矩形混淆。

const svg = document.querySelector('#sound__icon')
const btn = document.querySelector('.mute')
const rects = svg.children
let mute = false

function muting()
    if(!mute)
        mute = true
      for(let rect of rects)
            rect.style.height = '30%'
      
    
    else
        mute = false
        audioAnimActivate()
    


function audioAnimActivate()
    for(let rect of rects)
        rect.classList.add('rect-anim')
    
    svg.classList.add('sound__icon--on')


function audioAnimFreeze()
    for(let rect of rects)
        let rectHeight = window.getComputedStyle(rect).height
        rect.style.height = rectHeight
        rect.classList.remove('rect-anim')
        rect.style.transition = 'height .5s ease'
        rect.style.height = '50%'
    
    //? How to code this better ???
    setTimeout(() => svg.classList.remove('sound__icon--on'), 16)


btn.addEventListener('click', muting)
btn.addEventListener('mouseenter', () => 
    if(mute)
        rects[0].style.height = '60%'
        rects[1].style.height = '20%'
        rects[2].style.height = '40%'
        rects[3].style.height = '80%'
    
    else
        audioAnimFreeze()
    
    
)
btn.addEventListener('mouseleave', () => 
    if(!mute)
        audioAnimActivate()
    
    else
        for(let rect of rects)
            rect.style.height = '30%'
        
    
) 
.mute
    width: 25vw;
    height: 25vw;
    border: none;
    display: flex;
    justify-content: center;
    align-items: center;
    background: none;


.rect-1, .rect-2, .rect-3, .rect-4
    width: 10%;
    height: 30%;


.sound__icon--on .rect-anim
    animation-name: sound-icon-anim;
    animation-duration: 1s;
    animation-timing-function: cubic-bezier(.97,0,0,1);
    animation-iteration-count: infinite;


.sound__icon--on .rect-1
    animation-delay: .1s;


.sound__icon--on .rect-2
    animation-delay: .3s;


.sound__icon--on .rect-3
    animation-delay: .5s;


.sound__icon--on .rect-4
    animation-delay: .7s;


@keyframes sound-icon-anim
    0%
        height: 30%;
    
    10%
        height: 70%;
    
    20%
        height: 50%;
    
    50%
        height: 80%;
    
    60%
        height: 100%;
    
    70%
        height: 70%;
    
    100%
        height: 30%;
    
    <div class="mute">
        <svg version="1.1" id="sound__icon" class="sound__icon--on"xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
        viewBox="0 0 100 100"   >
          <rect class="rect-1 rect-anim" x="15" fill="currentColor"></rect>
          <rect class="rect-2 rect-anim" x="35" fill="currentColor"></rect>
          <rect class="rect-3 rect-anim" x="55" fill="currentColor"></rect> 
          <rect class="rect-4 rect-anim" x="75" fill="currentColor"></rect>
      </svg>
    </div>

【讨论】:

以上是关于无限 SVG 动画在悬停时平滑地检索初始状态的主要内容,如果未能解决你的问题,请参考以下文章

在链接悬停时为 SVG 设置动画

jQuery SVG - 悬停元素

逐渐为 SVG 设置动画

停止无限的 CSS3 动画并平滑恢复到初始状态

CSS3:动画之间的平滑过渡:hover

jQuery 将背景图像设置为菜单悬停状态和菜单活动状态