Sinusoidal ASCII-art animated text
HTML + ES6, 241 244 237 bytes
Breakdown:

HTML: 16 bytes
JS function: 221 bytes



let f =

s=&gt;setInterval(_=&gt;o.innerHTML=[...s].map((c,i)=&gt;([j,y]=[...Array(40)].map((_,k)=&gt;[j=k%20,y,y+=77732&gt;&gt;j&amp;1?k&lt;20||-1:0],y=0)[(i+p)%40],a[y]=a[y]||[...s].fill` `)[x]=(x+=j!=9,c),x=0,a=[],p++)&amp;&amp;a.map(r=&gt;r.join``).join`
`,p=30)

f("This is Programming Puzzles and Code Golf! Yay!")
&lt;pre id=o&gt;&lt;/pre&gt;



How?
Building the path
The following code builds the path:
[...Array(40)].map((_, k) =&gt;
  [
    j = k % 20,
    y,
    y += 77732 &gt;&gt; j &amp; 1 ? k &lt; 20 || -1 : 0
  ],
  y = 0
)

This gives an array of arrays [j, y, z] where j is the position modulo 20, y is the y-coordinate at this position and z is not used later (it just happens to be computed here to save some bytes).
Because the path is symmetric, we only need to encode the 20 first positions. We do that by using a binary number where each 1 bit means that y must be updated (+1 for the first half, -1 for the second half).
001
   001
      01
        1
         1
         1
          1
           01
             001
                000

With the first position being mapped to the least significant bit, this gives:
00010010111110100100 as binary = 77732 as decimal

Because this binary number is itself also symmetric, we can read it in the same order for the 2nd half.
Hence the formula:
y += (77732 &gt;&gt; j) &amp; 1 ? (k &lt; 20 ? 1 : -1) : 0

which can also be written as:
y += (77732 &gt;&gt; j) &amp; 1 ? k &lt; 20 || -1 : 0

where k is the position and j is the position modulo 20.
Updating x is much easier: we just have one special case to detect by comparing the position modulo 20 with 9.
Drawing the text
In the following code, the path building code described above has been replaced by a path variable for readability.
s =&gt; setInterval(                       // set a periodic timer:
  _ =&gt;                                  //   with an anonymous callback
    o.innerHTML =                       //   which updates the content of 'o'
      [...s].map((c, i) =&gt; (            //   for each character c of the string s
          [j, y] = path[(i + p) % 40],  //     retrieve j and y from the path
          a[y] = a[y] || [...s].fill` ` //     initialize a[y] if it's undefined
        )[x] = (x += j! = 9, c),        //     update a[y][x] / update x
        x = 0,                          //     initialize x
        a = [],                         //     initialize a[]
        p++                             //     increment the phase
      ) &amp;&amp;                              //   end of map()
      a.map(r =&gt; r.join``).join`\n`,    //   join everything together
  p = 30                                //   initial phase = interval period = 30
)                                       // end of timer definition