Search code examples
cssanimationsvgcss-animationssvg-animate

Keep fill of SVG pattern centered in animation


I have an animated SVG in which the origin is the center. There is 256x256 path that is centered around the origin. This path has a fill which is a pattern that is a gradient rect also of 256x256. The path is animated to expand to the full width and height of the viewbox. Ideally, I would like the path's fill to stay centered around the origin, however, it stays in the top left of path, therefore moving with the animation.

I've tried animating the fill (x, y and transform properties) to fake the fill staying centered however this has cros-browser issues. I tried animating all the properties in both CSS and plain SVG and each way tends to work on one browser not the other. For example, the animateTransform spline easings don't work on Chrome, but they do on FireFox. Alternatively, the CSS cubic-bezier animation easings do work on Chrome but don't on FireFox.

Here is the current SVG:

<svg
  width="1024"
  height="1024"
  viewBox="-512 -512 1024 1024"
  fill="none"
  overflow="hidden"
  xmlns="http://www.w3.org/2000/svg"
  preserveAspectRatio="xMinYMin meet"
>
  <style>
    #cube {
      transform-box: fill-box;
      transform: translate(-50%, -50%);
      z-index: 1;
    }

    #origin,
    #origin_label {
      fill: red;
      stroke: darkred;
      stroke-width: 0;
      font-family: sans-serif;
      font-size: 30px;
    }
  </style>

  <circle id="origin" cx="0" cy="0" r="10" />
  <text id="origin_label" x="15" y="30">(0, 0)</text>

  <use href="#cube" stroke-width="2">
    <animate
      attributeName="stroke"
      dur="6s"
      repeatCount="indefinite"
      values="#FF9AA2;#FFB7B2;#FFDAC1;#E2F0CB;#B5EAD7;#C7CEEA;#FF9AA2"
    />
  </use>

  <defs>
    <!-- EVERYTHING TOGETHER -->
    <g id="cube">
      <use
        href="#cube_outline"
        stroke-linejoin="round"
        stroke-width="16"
        fill="url(#sky_bg)"
      />
    </g>

    <!-- GRADIENT FILL -->
    <pattern
      id="sky_bg"
      x="0%"
      y="0%"
      width="100%"
      height="100%"
      patternUnits="userSpaceOnUse"
      patternContentUnits="userSpaceOnUse"
    >
      <rect
        width="256"
        height="256"
        x="0"
        y="0"
        fill="url(#sky)"
        id="starpat"
      ></rect>
    </pattern>

    <!-- THICK OUTER LINE-->
    <g id="cube_outline">
      <path>
        <animate
          attributeName="d"
          dur="3s"
          calcMode="spline"
          keyTimes="0;0.5;1"
          repeatCount="1"
          fill="freeze"
          keySplines="0.4 0 0.2 1;0.4 0 0.2 1"
          values="M 0 64 L 118 0 L 236 64 L 236 192 L 118 256 L 0 192 Z;
          M 0 0 L 236 0 L 236 0 L 236 256 L 0 256 L 0 256 Z;
          M 0 0 L 1010 0 L 1010 0 L 1010 1010 L 0 1010 L 0 1010 Z"
        />
      </path>
    </g>

    <linearGradient id="sky" gradientTransform="rotate(90)">
      <stop offset="0.5" stop-color="#141417" />
      <stop offset="1" stop-color="#40354a" />
    </linearGradient>
  </defs>
</svg>

I am limited to only CSS, HTML and SVG as any javascript will be sanitised away. Is it possible to keep the fill centered? Or is there another approach I should take?


Solution

  • Since you are grouping svg shapes together the width and height of <g> tag changes when the cube_outline starts to expand. In order to avoid that i just used a second <use> tag and removed the to avoid grouping and changed the rect to a path to mimic the same animation that outline does before its expansion. Hope this solves your problem.

        <svg
    width="1024"
    height="1024"
    viewBox="-512 -512 1024 1024"
    fill="none"
    overflow="hidden"
    xmlns="http://www.w3.org/2000/svg"
    preserveAspectRatio="xMinYMin meet"
    >
    <style>
      #cube_outline,#starpat {
        transform-box: fill-box;
        transform: translate(-50%, -50%);
        z-index: 1;
      }
      
      #origin,
      #origin_label {
        fill: red;
        stroke: darkred;
        stroke-width: 0;
        font-family: sans-serif;
        font-size: 30px;
      }
    </style>
      
    <circle id="origin" cx="0" cy="0" r="10" />
    <text id="origin_label" x="15" y="30">(0, 0)</text>
      <use
        href="#cube_outline"
        stroke-linejoin="round"
        stroke-width="16"
      />
      <use
        href="#starpat"
      />
    <defs>
      <!-- EVERYTHING TOGETHER -->
    
      
      <!-- GRADIENT FILL -->
        <path
          width="256"
          height="256"
          x="0"
          y="0"
          fill="url(#sky)"
          id="starpat"
        >
          <animate
            attributeName="d"
            dur="1.5s"
            calcMode="spline"
            keyTimes="0;1"
            repeatCount="1"
            fill="freeze"
            keySplines="0.4 0 0.2 1"
            values="M 0 64 L 113 0 L 226 64 L 226 187 L 113 246 L 0 182 Z;
            M 0 0 L 226 0 L 226 0 L 226 246 L 0 246 L 0 246 Z;"
          />
      </path>
      
      <!-- THICK OUTER LINE-->
      <g id="cube_outline">
        <path>
          <animate
            attributeName="d"
            dur="3s"
            calcMode="spline"
            keyTimes="0;0.5;1"
            repeatCount="1"
            fill="freeze"
            keySplines="0.4 0 0.2 1;0.4 0 0.2 1"
            values="M 0 64 L 118 0 L 236 64 L 236 192 L 118 256 L 0 192 Z;
            M 0 0 L 236 0 L 236 0 L 236 256 L 0 256 L 0 256 Z;
            M 0 0 L 1010 0 L 1010 0 L 1010 1010 L 0 1010 L 0 1010 Z"
          />
          <animate
            attributeName="stroke"
            dur="6s"
            repeatCount="indefinite"
            values="#FF9AA2;#FFB7B2;#FFDAC1;#E2F0CB;#B5EAD7;#C7CEEA;#FF9AA2"
          />
        </path>
      </g>
      
      <linearGradient id="sky" gradientTransform="rotate(90)">
        <stop offset="0.5" stop-color="#141417" />
        <stop offset="1" stop-color="#40354a" />
      </linearGradient>
    </defs>
      </svg>