skip to Main Content

I try to animate the small bubbles in my svg. But when scale is applied it looks like it’s applied to the distances between the bubbles rather than the bubbles only. It makes the buble to move to the side rather then up only as I’d like them. Why? And how to fix it? The transform-origin only changes the direction all of them moves.

svg {
position: absolute;
left: 200px;
top: 200px;
overflow: visible;
}

@keyframes bubble {
        0% {
            opacity: 1;
            transform: translateY(0) scale(1);
        }
        50% {
            opacity: 1;
            transform: translateY(-50px) scale(0.5);
        }
        100% {
            opacity: 0;
            transform: translateY(-100px) scale(0);
        }
    }

#circle1,
#circle2,
#circle3,
#circle4 {

transform-origin: top;
  animation: bubble 4s ease-in-out infinite;
}
<svg width="500" height="1000" xmlns="http://www.w3.org/2000/svg">
  <circle id="circle1" cx="30" cy="30" r="10" fill="red"/>
  <circle id="circle2" cx="70" cy="30" r="10" fill="blue"/>
  <circle id="circle3" cx="30" cy="70" r="10" fill="green"/>
  <circle id="circle4" cx="70" cy="70" r="10" fill="yellow"/>
</svg>

2

Answers


  1. It’s because of the scale but also width and transform-origin play part. Most the of issues are resolved when the width is indeed 100. Also, you can make the transform-origin: left to make it appear to stay on same x position. Besides you can apply the transform to the entire svg rather than it’s inner circles.

    svg {
      position: absolute;
      left: 200px;
      top: 100px;
      overflow: visible;
      border: 1px solid red;
    }
    
    @keyframes bubble {
      0% {
        opacity: 1;
        transform: translateY(0) scale(1);
      }
      50% {
        opacity: 1;
        transform: translateY(-50px) scale(0.5);
      }
      100% {
        opacity: 0;
        transform: translateY(-100px) scale(0);
      }
    }
    
    svg {
      transform-origin: top center;
      animation: bubble 4s ease-in-out infinite;
    }
    <svg width="100" xmlns="http://www.w3.org/2000/svg">
      <circle id="circle1" cx="30" cy="30" r="10" fill="red"/>
      <circle id="circle2" cx="70" cy="30" r="10" fill="blue"/>
      <circle id="circle3" cx="30" cy="70" r="10" fill="green"/>
      <circle id="circle4" cx="70" cy="70" r="10" fill="yellow"/>
    </svg>
    Login or Signup to reply.
  2. transform-origin: top sets the transform origin to the middle of the top edge of the <svg> element (in your case 250px 0), so all transforms happen in relation to that point. You probably want something like this:

    svg {
      background: pink; /* visualize bounds */
      margin-top: 100px;
      overflow: visible;
    }
    
    @keyframes bubble {
      0% {
        transform: translateY(0) scale(1);
      }
      50% {
        opacity: 1;
        transform: translateY(-50px) scale(0.5);
      }
      100% {
        opacity: 0;
        transform: translateY(-100px) scale(0);
      }
    }
    
    circle {
      animation: bubble 4s ease-in-out infinite;
    }
    
    
    /* solution 2 */
    #circle1 { transform-origin: 30px 30px }
    #circle2 { transform-origin: 70px 30px }
    #circle3 { transform-origin: 30px 70px }
    #circle4 { transform-origin: 70px 70px }
    <!-- solution 1 -->
    <svg width="100" height="100">
      <g transform="translate(30, 30)">
        <circle r="10" fill="red"/>
      </g>
      <g transform="translate(70, 30)">
        <circle r="10" fill="blue"/>
      </g>
      <g transform="translate(30, 70)">
        <circle r="10" fill="green"/>
      </g>
      <g transform="translate(70, 70)">
        <circle r="10" fill="yellow"/>
      </g>
    </svg>
    
    <!-- solution 2 (see css) -->
    <svg width="100" height="100">
      <circle id="circle1" cx="30" cy="30" r="10" fill="red"/>
      <circle id="circle2" cx="70" cy="30" r="10" fill="blue"/>
      <circle id="circle3" cx="30" cy="70" r="10" fill="green"/>
      <circle id="circle4" cx="70" cy="70" r="10" fill="yellow"/>
    </svg>

    You can then position the <svg> however you want.

    If you want to optimize the rendering, you should avoid using overflow: visible and instead expand the SVG to accommodate for the animation, and maybe use contain: paint, like so:

    svg {
      background: pink; /* visualize bounds */
      contain: paint;
      margin-top: -100%;
    }
    
    @keyframes bubble {
      0% {
        transform: translateY(0) scale(1);
      }
      50% {
        opacity: 1;
        transform: translateY(-50px) scale(0.5);
      }
      100% {
        opacity: 0;
        transform: translateY(-100px) scale(0);
      }
    }
    
    circle {
      animation: bubble 4s ease-in-out infinite;
    }
    
    /* example container */
    div {
      display: grid;
      min-height: 180px;
      outline: pink solid;
      place-content: center;
    }
    <div>
      <svg width="50" height="100" viewBox="0 -100 100 200">
        <g transform="translate(30, 30)">
          <circle r="10" fill="red"/>
        </g>
        <g transform="translate(70, 30)">
          <circle r="10" fill="blue"/>
        </g>
        <g transform="translate(30, 70)">
          <circle r="10" fill="green"/>
        </g>
        <g transform="translate(70, 70)">
          <circle r="10" fill="yellow"/>
        </g>
      </svg>
    </div>

    Here we’re "raising the ceiling" in the <svg>, and lowering it again with margin-top: -100%, so the image can be centered properly. You can also scale the SVG using width/height, as long as you provide a viewBox

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search