How can I use RxJS to generate a requestAnimationFrame loop?

It's because the default behaviour of Observable.of is to emit immediately.

To change this behaviour, you should specify the Scheduler when calling Observable.of:

let x = 0;

Rx.Observable
    .of(0, Rx.Scheduler.animationFrame)
    .repeat()
    .takeUntil(Rx.Observable.timer(1000))
    .subscribe(() => console.log(x++));
<script src="https://npmcdn.com/@reactivex/[email protected]/dist/global/Rx.min.js"></script>

Or, more simply, replace the of and repeat operators with:

Observable.interval(0, Rx.Scheduler.animationFrame)

This is how I use requestAnimationFrame with rxjs. I've seen a lot of developers using 0 instead of animationFrame.now(). It's much better to pass the time because you often need that in animations.

const { Observable, Scheduler } = Rx;

const requestAnimationFrame$ = Observable
  .defer(() => Observable
    .of(Scheduler.animationFrame.now(), Scheduler.animationFrame)
    .repeat()
    .map(start => Scheduler.animationFrame.now() - start)
  );

// Example usage
const duration$ = duration => requestAnimationFrame$
  .map(time => time / duration)
  .takeWhile(progress => progress < 1)
  .concat([1])

duration$(60000)
  .subscribe((i) => {
    clockPointer.style.transform = `rotate(${i * 360}deg)`;
  });
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/Rx.js"></script>

<div style="border: 3px solid black; border-radius: 50%; width: 150px; height: 150px;">
  <div id="clockPointer" style="width: 2px; height: 50%; background: black; margin-left: 50%; padding-left: -1px; transform-origin: 50% 100%;"></div>
</div>