animationsvghovertransformscale

Trying to animate background element by hovering foreground element in SVG. I suspect I need :has() for that but can't figure out how


In my original code, both squares pulsate when hovering anywhere on the SVG canvas. But I need them to pulsate only when hovering over the smallest square. I suspect that :has() could solve this but can't figure out how to implement it. Perhaps it can be solved without :has()?

My original code:

.wrapper:hover .rect_small {
    animation: pulsate 2s ease forwards;
    transform-origin: center;
}
.wrapper:hover .rect_large {
    animation: pulsate 2s ease forwards 1s;
    transform-origin: center;
}
@keyframes pulsate {
    0% {
        fill: black;
        transform: scale(1);
    }
    5% {
        fill: red;
        transform: scale(1.5);
    }
    100% {
        fill: black;
        transform: scale(1);
    }
}



.g_large {
    animation: rotate_large 5s linear infinite;
    transform-origin: center;
}
@keyframes rotate_large {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}



.g_small {
    animation: rotate_small 5s linear infinite;
    transform-origin: center;
}
@keyframes rotate_small {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(720deg);
    }
}
<svg width="200" height="200" viewBox="0 0 200 200" fill="none">
<g class="wrapper">
<rect width="200" height="200" fill="black"/>
<g class="g_large"><rect class="rect_large" x="50" y="50" width="100" height="100" fill="black" stroke="white" stroke-width="2"/></g>
<g class="g_small"><rect class="rect_small" x="75" y="75" width="50" height="50" fill="black" stroke="white" stroke-width="2"/></g>
</g>
</svg>

My code with implementation of :has() as a potential solution but to no avail. When hovering over the smallest square, only the smallest square pulsates. The large square doesn't even with the :has() selector:

.rect_small:hover {
    animation: pulsate 2s ease forwards;
    transform-origin: center;
}
.rect_large:has(.rect_small:hover) {
    animation: pulsate 2s ease forwards 1s;
    transform-origin: center;
}
@keyframes pulsate {
    0% {
        fill: black;
        transform: scale(1);
    }
    5% {
        fill: red;
        transform: scale(1.5);
    }
    100% {
        fill: black;
        transform: scale(1);
    }
}



.g_large {
    animation: rotate_large 5s linear infinite;
    transform-origin: center;
}
@keyframes rotate_large {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}



.g_small {
    animation: rotate_small 5s linear infinite;
    transform-origin: center;
}
@keyframes rotate_small {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(720deg);
    }
}
<svg width="200" height="200" viewBox="0 0 200 200" fill="none">
<g class="wrapper">
<rect width="200" height="200" fill="black"/>
<g class="g_large"><rect class="rect_large" x="50" y="50" width="100" height="100" fill="black" stroke="white" stroke-width="2"/></g>
<g class="g_small"><rect class="rect_small" x="75" y="75" width="50" height="50" fill="black" stroke="white" stroke-width="2"/></g>
</g>
</svg>


Solution

  • The .rect_large is a sibling and not a parent of .rect_small. You can check with :has on the .wrapper the .rect_small was hovered. and then animate the large one:

    .rect_small:hover {
      animation: pulsate 2s ease forwards;
      transform-origin: center;
    }
    
    .wrapper:has(.rect_small:hover) .rect_large {
      animation: pulsate 2s ease forwards 1s;
      transform-origin: center;
    }
    
    @keyframes pulsate {
      0% {
        fill: black;
        transform: scale(1);
      }
    
      5% {
        fill: red;
        transform: scale(1.5);
      }
    
      100% {
        fill: black;
        transform: scale(1);
      }
    }
    
    
    
    .g_large {
      animation: rotate_large 5s linear infinite;
      transform-origin: center;
    }
    
    @keyframes rotate_large {
      0% {
        transform: rotate(0deg);
      }
    
      100% {
        transform: rotate(360deg);
      }
    }
    
    
    
    .g_small {
      animation: rotate_small 5s linear infinite;
      transform-origin: center;
    }
    
    @keyframes rotate_small {
      0% {
        transform: rotate(0deg);
      }
    
      100% {
        transform: rotate(720deg);
      }
    }
    <svg width="200" height="200" viewBox="0 0 200 200" fill="none">
      <g class="wrapper">
        <rect width="200" height="200" fill="black"/>
        <g class="g_large"><rect class="rect_large" x="50" y="50" width="100" height="100" fill="black" stroke="white" stroke-width="2"/></g>
        <g class="g_small"><rect class="rect_small" x="75" y="75" width="50" height="50" fill="black" stroke="white" stroke-width="2"/></g>
      </g>
    </svg>