htmljquerycsssvgcss-animations

SVG Animation: Text Moves along a circle


I have a spinning circle and a text moves along with this circle.

When the circle spins, the text needs to be positioned like the image below.

enter image description here

In CSS, I manually position the text in every 8.33% from @keyframes.

I didn't get it done for a complete circle (please take a look at my CSS) because it takes long time to adjust the text for right positions.

Is there any dynamic way, or an easy way to get it done? Moreover, I have several more texts move along this circle. I added more texts in and found: based on text's length, they rotated and landed on different position.

Please give me a hand and greatly appreciate!

Update: Thank you, Admin for letting me know that my question was duplicated with Rotate objects around circle using CSS?! I have tried all the associated answers on my case, but didn't get as expected:

  1. My case: the circle is rotating and it moves the text along. Whereas, the circle is not spinning in the associated answers.
  2. My case: the text (long text) should be stay outside the circle when the circle spinning.
  3. My case is using SVG.

#circle {
    /* transform-origin: 150px 150px; */
    animation: rotate 60s linear infinite ;
   /*  transform-box: fill-box; */
    transform-origin: center;
}
@keyframes rotate {
    from {
        rotate: 0;
    }
    to {
        rotate: 360deg;
    }
}

#TextSample{
    animation: figmaMove 60s linear infinite ;
    transform-box: fill-box;
    transform-origin: top left;
} 
@keyframes figmaMove {
    0% {
        translate: 0;
        transform: rotate(0);  
    }
    8.33% {
        translate: -7px 0;
        transform: rotate(-26deg); 
    }

    16.66% {
        translate: -10px 0;
        transform: rotate(-56deg); 
    }
    25% {
        translate: -20px 0;
        transform: rotate(-86deg); 
        transform-origin: top left;
    }
    33.33% {
        translate: -65px -35px;
        transform: rotate(-120deg); 
        transform-origin: bottom right;
    }
    41.66% {
        translate: -85px -45px;
        transform: rotate(-120deg); 
        transform-origin: top right;
    }
}
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     width="546.667px" height="492.667px" viewBox="0 0 546.667 492.667" enable-background="new 0 0 546.667 492.667"
     xml:space="preserve">

<radialGradient id="angles12_1_" cx="276.3926" cy="249.7295" r="175" gradientUnits="userSpaceOnUse">
    <stop  offset="0" style="stop-color:#FFFFFF"/>
    <stop  offset="1" style="stop-color:#000000"/>
</radialGradient>
<path id="angles12" opacity="0.23" fill="url(#angles12_1_)" d="M276.393,74.729c1.272,0,2.303,78.35,2.303,175
    c0,96.649-1.03,175-2.303,175c-1.271,0-2.303-78.351-2.303-175C274.09,153.08,275.121,74.729,276.393,74.729z M276.393,252.032
    c96.649,0,175-1.03,175-2.303c0-1.272-78.351-2.303-175-2.303c-96.649,0-175,1.031-175,2.303
    C101.393,251.002,179.744,252.032,276.393,252.032z M274.398,248.578c-48.324,83.701-86.607,152.069-85.505,152.705
    c1.102,0.637,41.169-66.702,89.494-150.402c48.325-83.701,86.607-152.07,85.506-152.706
    C362.791,97.539,322.723,164.877,274.398,248.578z M275.241,247.735c-83.7,48.324-151.039,88.393-150.402,89.494
    s69.005-37.181,152.705-85.506c83.701-48.325,151.04-88.393,150.403-89.494C427.312,161.128,358.942,199.411,275.241,247.735z
     M275.241,251.724c83.701,48.325,152.07,86.607,152.706,85.506c0.637-1.102-66.703-41.17-150.403-89.494
    c-83.7-48.325-152.069-86.607-152.705-85.506S191.541,203.398,275.241,251.724z M274.398,250.881
    c48.324,83.7,88.393,151.039,89.494,150.402c1.102-0.636-37.181-69.004-85.506-152.705
    c-48.324-83.701-88.392-151.039-89.494-150.403C187.792,98.811,226.074,167.18,274.398,250.881z"/>
<g id="circle" >
<path fill="none" stroke="#ED1C24" stroke-width="2" stroke-dasharray="9,9" d="M425.619,265.066
    c-7.682,75.638-71.562,134.663-149.226,134.663c-82.844,0-150.001-67.157-150.001-150c0-82.843,67.157-150,150.001-150
    c82.842,0,150,67.157,150,150"/>
<g id="TextSample">
    <path d="M419.292,160.197h-3.69v-1.333h8.984v1.333h-3.709V171h-1.585V160.197z"/>
    <path d="M425.483,166.931c0.036,2.143,1.404,3.025,2.989,3.025c1.135,0,1.818-0.198,2.413-0.45l0.27,1.134
        c-0.558,0.252-1.513,0.559-2.898,0.559c-2.683,0-4.285-1.783-4.285-4.412c0-2.628,1.548-4.699,4.087-4.699
        c2.845,0,3.601,2.502,3.601,4.105c0,0.324-0.035,0.576-0.054,0.738H425.483z M430.129,165.796c0.019-1.008-0.414-2.575-2.196-2.575
        c-1.603,0-2.305,1.477-2.431,2.575H430.129z"/>
    <path d="M434.304,162.285l1.242,1.873c0.324,0.486,0.594,0.936,0.883,1.422h0.054c0.288-0.522,0.576-0.973,0.864-1.44l1.225-1.855
        h1.71l-2.971,4.213l3.061,4.501h-1.8l-1.279-1.962c-0.342-0.504-0.63-0.991-0.936-1.513h-0.036c-0.288,0.54-0.595,0.99-0.918,1.513
        L434.142,171h-1.746l3.097-4.447l-2.953-4.268H434.304z"/>
    <path d="M444.041,160.197v2.088h2.269v1.207h-2.269v4.7c0,1.08,0.307,1.692,1.188,1.692c0.433,0,0.685-0.036,0.918-0.108
        l0.072,1.188c-0.306,0.126-0.792,0.234-1.404,0.234c-0.738,0-1.332-0.252-1.71-0.685c-0.45-0.468-0.612-1.242-0.612-2.269v-4.753
        h-1.351v-1.207h1.351v-1.602L444.041,160.197z"/>
    <path d="M451.745,169.091c0.702,0.432,1.729,0.792,2.809,0.792c1.603,0,2.539-0.846,2.539-2.071c0-1.134-0.648-1.783-2.287-2.413
        c-1.98-0.702-3.205-1.729-3.205-3.439c0-1.891,1.567-3.295,3.926-3.295c1.242,0,2.143,0.288,2.683,0.594l-0.432,1.278
        c-0.396-0.216-1.207-0.576-2.305-0.576c-1.657,0-2.287,0.99-2.287,1.818c0,1.134,0.738,1.692,2.413,2.341
        c2.053,0.792,3.097,1.782,3.097,3.565c0,1.873-1.387,3.511-4.249,3.511c-1.171,0-2.449-0.36-3.098-0.792L451.745,169.091z"/>
    <path d="M466.957,168.911c0,0.756,0.036,1.495,0.144,2.089h-1.44l-0.126-1.098h-0.054c-0.486,0.684-1.423,1.296-2.665,1.296
        c-1.765,0-2.665-1.243-2.665-2.503c0-2.106,1.873-3.259,5.24-3.241v-0.18c0-0.72-0.198-2.017-1.981-2.017
        c-0.81,0-1.656,0.252-2.269,0.648l-0.359-1.044c0.72-0.468,1.765-0.774,2.862-0.774c2.665,0,3.313,1.818,3.313,3.565V168.911z
         M465.427,166.553c-1.729-0.036-3.691,0.27-3.691,1.962c0,1.026,0.684,1.513,1.494,1.513c1.135,0,1.854-0.72,2.106-1.458
        c0.055-0.162,0.091-0.342,0.091-0.504V166.553z"/>
    <path d="M469.511,164.644c0-0.9-0.018-1.638-0.072-2.359h1.387l0.072,1.405h0.054c0.486-0.829,1.296-1.603,2.736-1.603
        c1.188,0,2.089,0.72,2.467,1.747h0.036c0.271-0.486,0.612-0.864,0.973-1.134c0.522-0.396,1.099-0.612,1.927-0.612
        c1.152,0,2.862,0.756,2.862,3.781V171h-1.548v-4.934c0-1.674-0.612-2.683-1.891-2.683c-0.9,0-1.603,0.666-1.873,1.44
        c-0.072,0.216-0.126,0.504-0.126,0.792V171h-1.549v-5.222c0-1.386-0.611-2.395-1.818-2.395c-0.99,0-1.71,0.792-1.962,1.584
        c-0.091,0.234-0.126,0.504-0.126,0.774V171h-1.549V164.644z"/>
    <path d="M484.522,165.13c0-1.117-0.036-2.017-0.072-2.845h1.423l0.071,1.495h0.036c0.648-1.062,1.675-1.692,3.098-1.692
        c2.106,0,3.69,1.783,3.69,4.429c0,3.133-1.908,4.682-3.961,4.682c-1.152,0-2.16-0.504-2.683-1.369h-0.036v4.735h-1.566V165.13z
         M486.089,167.453c0,0.234,0.036,0.45,0.072,0.648c0.288,1.098,1.242,1.854,2.377,1.854c1.674,0,2.646-1.368,2.646-3.367
        c0-1.747-0.918-3.241-2.593-3.241c-1.08,0-2.089,0.774-2.395,1.963c-0.055,0.198-0.108,0.432-0.108,0.648V167.453z"/>
    <path d="M494.765,158.216h1.584V171h-1.584V158.216z"/>
    <path d="M499.894,166.931c0.036,2.143,1.404,3.025,2.989,3.025c1.135,0,1.818-0.198,2.413-0.45l0.27,1.134
        c-0.558,0.252-1.513,0.559-2.898,0.559c-2.683,0-4.285-1.783-4.285-4.412c0-2.628,1.548-4.699,4.087-4.699
        c2.845,0,3.601,2.502,3.601,4.105c0,0.324-0.035,0.576-0.054,0.738H499.894z M504.539,165.796c0.019-1.008-0.414-2.575-2.196-2.575
        c-1.603,0-2.305,1.477-2.431,2.575H504.539z"/>
    <path d="M514.78,160.773h-0.036l-2.034,1.098l-0.307-1.206l2.558-1.369h1.35V171h-1.53V160.773z"/>
</g>

</g>
</svg>


Solution

  • You need the text to move over an ellipse so the text doesn't overlap the circle

    With SVG SMIL that ellipse needs to be a <path> for the <mpath> to work

    That means you need as many ellipses as labels (preventing duplicate ID errors!)

    Or extra animations to position each label; which I couldn't do in 5 minutes

    svg { height:180px }
    <svg viewBox="-30 0 200 120" style="background:pink">
        <path id="ellipse" d="M140,60 a70,50 0 1,1 -140,0 a70,50 0 1,1 140,0" 
              stroke="rebeccapurple" fill="none" />
        
        <circle r="25%" cx="70" cy="50%" fill="brown"/>
        <circle r="5%" cx="70" cy="50%" fill="grey"/>
    
        <text x="0" y="0" text-anchor="middle" dominant-baseline="middle">LABEL
            <animateMotion dur="10s" repeatCount="indefinite">
                <mpath href="#ellipse " />
            </animateMotion>
        </text>
    </svg>

    Update

    20 minutes trying to explain my view on Math to that AI idiot (savant) got me:

    rotating labels over ellipse

    <script>
      customElements.define("rotating-labels", class extends HTMLElement {
          connectedCallback() {
            function generateRotatedEllipsesPaths(cx, cy, rx, ry, n) {
              const paths = []; const angleStep = (2 * Math.PI) / n // Divide 360 degrees into n parts
              for (let i = 0; i < n; i++) {
                const angle = i * angleStep
                const startX = cx + rx * Math.cos(angle)
                const startY = cy + ry * Math.sin(angle)
                const endX = cx - rx * Math.cos(angle)
                const endY = cy - ry * Math.sin(angle)
                const d = `M ${startX},${startY}`+
                          `a${rx},${ry} 0 1,1 ${-2 * rx * Math.cos(angle)},${-2 * ry * Math.sin(angle)}`+
                          `a${rx},${ry} 0 1,1 ${2 * rx * Math.cos(angle)},${2 * ry * Math.sin(angle)}`
                paths.push(d)
              }
              return paths
            }
            const rotatedPaths = generateRotatedEllipsesPaths(140, 60, 70, 50, 12);
            this.innerHTML = `<svg viewBox="0 0 300 150" style="background:pink;width:80vw">` +           `<circle cx="140" cy="60" r="40" fill="lightgreen"/>` + 
              rotatedPaths
                .map((d, idx) => {
                  return `<path id="e${idx}" fill="none" stroke="grey" d="${d}"/>` +
                  `<text x="0" y="0" font-size="60%" fill="blue" text-anchor="middle" dominant-baseline="middle">` +
                  "Label " + (idx+1) + `<animateMotion dur="10s" repeatCount="indefinite">` +
                     `<mpath href="#e${idx}" /></animateMotion>` +
                  `</text>`}).join("") + `</svg>`
          }
        },
      )
    </script>
    <rotating-labels></rotating-labels>

    HTH