1

I made a text-along path outside a circle, and when I clicked the next button, the circle changes size randomly. I already made the circle transition smoothly. But I also want the text/textPath element also animating smoothly. As far as I know, SVG text can't be transitioned(?), are there any work around?

code in svelte:

<script>
    let r = 100
    let bez = 4*(Math.sqrt(2)-1)/3*r
    let cx = 250
    let cy = 200
    
    function next() {
        r = Math.random() *100
        bez = 4*(Math.sqrt(2)-1)/3*r
    }
    
</script>

<svg>
    <g>
        <circle cx="{cx}" cy="{cy}" r="{r*0.9}" opacity="0.05"></circle>
        <path stroke="cornflowerblue" 
                    stroke-dasharray="4"
                    fill="none" 
                    d="M{cx},{cy-r} 
                         C{cx+bez},{cy-r} {cx+r},{cy-bez} {cx+r},{cy} 
                         C{cx+r},{cy+bez} {cx+bez},{cy+r} {cx},{cy+r} 
                         C{cx-bez},{cy+r} {cx-r},{cy+bez} {cx-r},{cy}
                         C{cx-r},{cy-bez} {cx-bez},{cy-r} {cx},{cy-r}"
                    id="circle-text"></path>
        <text fill="red">
            <textPath href="#circle-text" >
                Lorem Ipsum Dolor Sit Amet
            </textPath>
        </text>
    </g>
</svg>
<button on:click={next}>
    next
</button>

<style>
    svg {
        width:500px;
        height:400px;
        background-color:lightgrey;
    }
    circle {
        transition:all 500ms ease-in-out;
    }
    path {
        transition:all 500ms ease-in-out;
    }
    text {
        transition:all 500ms ease-in-out;
    }
    textPath {
        transition:all 500ms ease-in-out;
    }
</style>

REPL link: https://svelte.dev/repl/fa34814ccacb4ce7a27fb40dc7c7a493?version=3.55.1

louislugas
  • 147
  • 6

2 Answers2

2

You could make the value of r a tweened value and then make bez a reactive declaration instead of setting it in your next function.

This way you're not relying on CSS to animate the transition of values and instead smoothly updating the values yourself. You can also lose all of the CSS transition rules. Here's an example in the REPL.

<script>
    import {tweened} from 'svelte/motion'
    import {quartInOut} from 'svelte/easing'
    const r = tweened(100, {
        duration: 500,
        easing: quartInOut
    })
    $: bez = 4*(Math.sqrt(2)-1)/3*$r
    let cx = 250
    let cy = 200

    function next() {
        $r = Math.random() *100
    }

</script>

<svg>
    <g>
        <circle cx="{cx}" cy="{cy}" r="{$r*0.9}" opacity="0.05"></circle>
        <path stroke="cornflowerblue" 
            stroke-dasharray="4"
            fill="none" 
            d="M{cx},{cy-$r} 
                C{cx+bez},{cy-$r} {cx+$r},{cy-bez} {cx+$r},{cy} 
                C{cx+$r},{cy+bez} {cx+bez},{cy+$r} {cx},{cy+$r} 
                C{cx-bez},{cy+$r} {cx-$r},{cy+bez} {cx-$r},{cy}
                C{cx-$r},{cy-bez} {cx-bez},{cy-$r} {cx},{cy-$r}"
                id="circle-text"></path>
        <text fill="red">
            <textPath href="#circle-text" >
                Lorem Ipsum Dolor Sit Amet
            </textPath>
        </text>
    </g>
</svg>
<button on:click={next}>
    next
</button>

<style>
    svg {
        width:500px;
        height:400px;
        background-color:lightgrey;
    }
</style>
JHeth
  • 7,067
  • 2
  • 23
  • 34
  • I'm still learning the concept of tweening and store/subscription, didn't have a good grasp on it. But thanks it's working as I want it to. – louislugas Mar 05 '23 at 07:41
1

You could also animate/transition the textPath adding an <animate> element.

    <animate href="#circlePath" attributeName="d" attributeType="XML" dur="1s" fill="freeze" 
to="M 50 5
      a  45 45 0 0 1  0 90
      a  45 45 0 0 1  0 -90
      " 
from="M 50 5
      a  45 45 0 0 1  0 90
      a  45 45 0 0 1  0 -90" />

We're defining start end end states by from and toattributes.

Each time, the new path data is randomized, we need to update the from and to values accordingly and restart the animation via animate.beginElement().

Plain JS example

let r = 50
let cx = 50
let cy = 50;

function changeRadius(){

  let min=0.5;
  let max= 1.5;
  let scale = +(Math.random() * (max - min) + min).toFixed(2);
  r = 50 * scale;
    
  let d= circlePath.getAttribute('d');
  
  let newD = `M ${cx } ${cy -r}
  a  ${r} ${r} 0 0 1  0 ${r*2}
  a  ${r} ${r} 0 0 1  0 -${r*2}
  `;
  
  // update smil animation element
  let animate = svg.querySelector('animate');
  animate.setAttribute('to', newD );
  animate.beginElement();
  circle.setAttribute('r', r);


  // update pathdata d attribute after animation 
  animate.addEventListener('endEvent', e=>{
      animate.setAttribute('from', newD);
      circlePath.setAttribute('d', newD); 
  });
  
}
body{
  margin:2em
}

svg{
  overflow:visible;
  width:10em;
  margin:2em;
}


button{
  cursor:pointer;
}

circle{
transition:r 1s linear;
}
<p><button onclick="changeRadius()">change radius</button></p>

<svg id="svg" viewBox="0 0 100 100">
  <g>
    <circle id="circle" cx="50" cy="50" r="50" opacity="0.05" />
    <path id="circlePath"
stroke="cornflowerblue" 
stroke-dasharray="4" 
fill="none" 
d="M 50, 0
a 1 1 0 0 1 0 100
a 1 1 0 0 1 0 -100"/>

    <animate href="#circlePath" attributeName="d" attributeType="XML" dur="1s" fill="freeze" />

    <text fill="red">
      <textPath id="textPath" href="#circlePath">
        Lorem Ipsum Dolor Sit Amet
      </textPath>
    </text>
  </g>
</svg>

REPL example

See this answer by Robert Longson: "SVG animate path's d attribute"

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34