Search code examples
csssvgcss-transforms

How can I rotate a symbol around its center without specifying its position twice?


I have some tile symbols that I would like to place on a grid. This is what I'm trying to achieve:

  • Use tile coordinates to position the symbols (e.g. 4,2 instead of 85,43)
  • Use pixel coordinates (not tile coordinates) for defining the vertices of symbols
  • Rotate symbols around their center without specifying absolute coordinates.

I have solutions to the first two (there might be better solutions though) but not the third. I can rotate the tile at 4,2 by a quarter turn with this:

<!-- 10/21 = 0.47619047619 -->
<use x="4" y="2" href="#rooftop-0" class="theme-0" transform="rotate(90 4.476 2.476)" />

I really don't like having to specify the tile coordinates twice. Ideally, I would like to write something like this instead:

<use x="4" y="2" href="#rooftop-0" class="theme-0 rotate-1" />

Defining .rotate-1 in the stylesheet doesn't seem to have any effect on the rotation. transform-origin="50% 50%" seems to be setting the origin to 50% of the viewport or something. Maybe defining the symbol with coordinates from -10 to 10 instead of 0 to 20 would help? Should I define the viewBox of the symbols?

Another solution would be manually changing the coordinates of the vertices in the symbol to create the other 3 orientations. I'd rather not!

Anyway, this is what I have so far:

<?xml version="1.0" standalone="no"?>
<!-- 21*10+1 = 211 -->
<svg width="211" height="211" version="2.0" xmlns="http://www.w3.org/2000/svg">
  <style>
    .grid-line {
      fill: #DDD;
    }
    .grass-fill {
      fill: #8C8;
    }
    .tile {
      /* 1/21 = 0.04761904761 */
      transform: scale(0.04761904761, 0.04761904761);
    }
    
    .theme-0 {
      --roof-color-0: #F44;
      --roof-color-1: #F66;
      --roof-color-2: #F88;
      --roof-color-3: #FAA;
    }
    
    .theme-1 {
      --roof-color-0: #44F;
      --roof-color-1: #66F;
      --roof-color-2: #88F;
      --roof-color-3: #AAF;
    }
  </style>
  
  <defs>
    <pattern id="grid" width="21" height="21" patternUnits="userSpaceOnUse">
      <rect class="grid-line" x="0" y="0" width="1" height="21" />
      <rect class="grid-line" x="0" y="0" width="21" height="1" />
    </pattern>
    
    <symbol id="rooftop-0">
      <g class="tile">
        <rect class="grass-fill" width="20" height="20" />
        <polygon style="fill: var(--roof-color-0)" points="3,2 17,2 18,8 2,8" />
        <polygon style="fill: var(--roof-color-1)" points="2,8 18,8 17,14 3,14" />
        <polygon style="fill: var(--roof-color-2)" points="8,8 11,14 11,18 8,18" />
        <polygon style="fill: var(--roof-color-3)" points="8,8 8,18 5,18 5,14" />
      </g>
    </symbol>
    
    <symbol id="rooftop-1">
      <g class="tile">
        <rect class="grass-fill" width="20" height="20" />
        <polygon style="fill: var(--roof-color-0)" points="2,11 18,11 17,17 3,17" />
        <polygon style="fill: var(--roof-color-1)" points="3,5 17,5 18,11 2,11" />
        <polygon style="fill: var(--roof-color-2)" points="10,11 10,3 13,3 13,5" />
        <polygon style="fill: var(--roof-color-3)" points="10,11 7,5 7,3 10,3" />
      </g>
    </symbol>
  </defs>
  
  <g>
    <rect fill="#999" width="100%" height="100%" />
    <rect fill="url(#grid)" width="100%" height="100%" />
  </g>
  
  <g transform="translate(1, 1) scale(21, 21)">
    <use x="3" y="2" href="#rooftop-0" class="theme-1" />
    <!-- 10/21 = 0.47619047619 -->
    <use x="4" y="2" href="#rooftop-0" class="theme-0" transform="rotate(90 4.476 2.476)" />
    <use x="5" y="2" href="#rooftop-1" class="theme-1" />
  </g>
</svg>

Here's a screenshot:

Screenshot

Is there a clean way of doing what I'm trying to do? Rotating a symbol around its center seems like something one would do all the time.


Solution

  • Instead of setting your <use> attributes using xand y coordinates, you can set them using transform="translate(x, y), this way, the origin parameters of the rotate() method will stay the same (0.5, 0.5):

    <svg viewBox="0 0 211 211">
      <style>
        .grid-line {
          fill: #DDD;
        }
        .grass-fill {
          fill: #8C8;
        }
        .theme-0 {
          --roof-color-0: #F44;
          --roof-color-1: #F66;
          --roof-color-2: #F88;
          --roof-color-3: #FAA;
        }
    
        .theme-1 {
          --roof-color-0: #44F;
          --roof-color-1: #66F;
          --roof-color-2: #88F;
          --roof-color-3: #AAF;
        }
      </style>
    
      <defs>
        <pattern id="grid" x="-0.5" y="-0.5" width="20" height="20" patternUnits="userSpaceOnUse">
          <rect class="grid-line" x="0" y="0" width="1" height="20" />
          <rect class="grid-line" x="0" y="0" width="20" height="1" />
        </pattern>
    
        <symbol id="rooftop-0" viewBox="0 0 20 20">
          <g class="tile" transform="translate(0.5, 0.5) scale(0.95)">
            <rect class="grass-fill" width="20" height="20" />
            <polygon style="fill: var(--roof-color-0)" points="3,2 17,2 18,8 2,8" />
            <polygon style="fill: var(--roof-color-1)" points="2,8 18,8 17,14 3,14" />
            <polygon style="fill: var(--roof-color-2)" points="8,8 11,14 11,18 8,18" />
            <polygon style="fill: var(--roof-color-3)" points="8,8 8,18 5,18 5,14" />
          </g>
        </symbol>
    
        <symbol id="rooftop-1" viewBox="0 0 20 20">
          <g class="tile"transform="translate(0.5, 0.5) scale(0.95)">
            <rect class="grass-fill" width="20" height="20" />
            <polygon style="fill: var(--roof-color-0)" points="2,11 18,11 17,17 3,17" />
            <polygon style="fill: var(--roof-color-1)" points="3,5 17,5 18,11 2,11" />
            <polygon style="fill: var(--roof-color-2)" points="10,11 10,3 13,3 13,5" />
            <polygon style="fill: var(--roof-color-3)" points="10,11 7,5 7,3 10,3" />
          </g>
        </symbol>
      </defs>
      
      <g>
        <rect fill="#999" width="100%" height="100%" />
        <rect fill="url(#grid)" width="100%" height="100%" />
      </g>
      
      <g transform="scale(20, 20)">
          <use xlink:href="#rooftop-0" class="theme-1" width="1" height="1" transform="translate(4, 2) rotate(0,0.5,0.5)"/>
          <use xlink:href="#rooftop-1" class="theme-0" width="1" height="1" transform="translate(5, 2) rotate(90,0.5,0.5)"/>
          <use xlink:href="#rooftop-0" class="theme-1" width="1" height="1" transform="translate(6, 2) rotate(180,0.5,0.5)"/>
      </g>
    </svg>