メインコンテンツまでスキップ

AnimationCanvas

requestAnimationFrame() でアニメーションできる、シンプルなキャンバス。

Source
Loading...
function App() {
  return (
    <AnimationCanvas
      width={200}
      height={200}
      init={(ctx) => {
        ctx.font = '16px sans-serif'
      }}
      draw={(ctx, { width, height, count }) => {
        ctx.clearRect(0, 0, width, height)
        ctx.fillText(`frame: ${count}`, 0, 16)
        // draw sine wave
        const halfH = height / 2
        ctx.strokeStyle = '#29bbf0'
        ctx.beginPath()
        for (let i = 0; i < width; i++) {
          const y =
            halfH +
            halfH * 0.5 * Math.sin((4 * Math.PI * (i + count * 2)) / width)
          if (i == 0) ctx.moveTo(i, y)
          else ctx.lineTo(i, y)
        }
        ctx.stroke()
      }}
    />
  )
}

インポート​

import { AnimationCanvas } from '@tremolo-ui/react'

特長​

  • devicePixelRatio に合わせます。
  • リサイズしたときのちらつきを抑えます。
  • 親要素の大きさを監視します。
  • シンプルなティッカーとタイマー

API リファレンス​

AnimationCanvas は <canvas> を描画し、その属性(className や style など)を受け取ります。ref は <canvas> に届きます。

Svelte / Vue でも props は同じで、書き方だけがそれぞれの流儀になります(className ではなく class、bind:value や v-model、中身は snippet やスロットなど)。Svelte と Vue を参照してください。

Prop型既定値
draw*CanvasDrawFunction—
initCanvasInitFunction—
animatebooleantrue
optionsCanvasRenderingContext2DSettings—
reduceFlickeringbooleantrue

すべての説明: AnimationCanvasCommonProps

Fixed と Resizable​

キャンバスは既定で固定サイズで、width と height が CSS ピクセルでの大きさを決めます。resizable を付けると、代わりに親要素の大きさに合わせるので、親要素が自分の大きさを持っている必要があります。2 つは排他的で、resizable と一緒に width や height を渡すと型エラーになります。

Fixed​

Prop型既定値
resizablefalse—
widthnumber100
heightnumber100

すべての説明: AnimationCanvasFixedProps

Resizable​

Prop型既定値
resizable*true—
widthundefined—
heightundefined—

すべての説明: AnimationCanvasResizableProps

Reactive Canvas​

既定では、キャンバスはアニメーションフレームごとに描き直します。描画が時間ではなく state で決まるときは animate={false} にします。するとキャンバスは、描く理由があるときだけ描きます。マウントしたとき、リサイズされたとき、そしてコンポーネントが再レンダーされたときです。

最後の 1 つがあるので、React.useState をそのまま描画の元にできます。新しい draw を渡せばキャンバスが描き直すので、メモ化するものも、手で起こす再描画もありません。

動く例は下の Reactive にあります。

ヒント

どちらの場合でも、draw はインラインで書いて構いません。キャンバスは 1 度だけ作られ、ハンドラはその場で差し替えられるので、再レンダーでアニメーションが最初からやり直しになったり、count や elapsedTime がリセットされたり、init がもう 1 度走ったりはしません。コンポーネントを React.memo で包んだり、draw を useCallback で持ったりする必要はありません。

例​

Reactive​

animate={false} にして、時間ではなく state で描くキャンバス。スライダーを動かすとコンポーネントが再レンダーされ、それがキャンバスを描き直します。

Loading...
function App() {
  const [hue, setHue] = useState(200)

  return (
    <div
      style={{
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        gap: 8,
      }}
    >
      <AnimationCanvas
        animate={false}
        width={240}
        height={80}
        draw={(ctx, { width, height }) => {
          ctx.clearRect(0, 0, width, height)
          for (let x = 0; x < width; x += 12) {
            ctx.fillStyle = `hsl(${(hue + x) % 360} 90% 60%)`
            ctx.fillRect(x, 0, 10, height)
          }
        }}
      />
      <Slider.Root
        className={sliderTheme.root}
        value={hue}
        min={0}
        max={360}
        onChange={(v) => setHue(v)}
      >
        <Slider.Track className={sliderTheme.track}>
          <Slider.Thumb className={sliderTheme.thumb} aria-label="Hue" />
        </Slider.Track>
      </Slider.Root>
    </div>
  )
}

Save / Restore​

Loading...