Skip to content

duxapp/react-native-canvas

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@duxapp/react-native-canvas

@duxapp/react-native-canvas is a React Native canvas-like drawing library built on top of @shopify/react-native-skia, with Canvas, Path2D, OffscreenCanvas, Image, and a familiar Canvas 2D style API. It currently supports 2d only and does not support WebGL or other 3D rendering contexts.

Table of Contents

Key Features

  • Canvas component for React Native
  • Canvas-like 2D context API
  • 2D rendering only, no WebGL / 3D context support
  • Path2D
  • OffscreenCanvas
  • Image
  • useClickable
  • text drawing and measuring
  • transforms and save/restore state
  • gradients, patterns, shadows, composite operations
  • toDataURL()
  • recommended picture rendering mode for higher frame rate in most drawing scenarios
  • built-in DPI handling, so you should not apply extra PixelRatio / devicePixelRatio scaling yourself

Installation

yarn add @duxapp/react-native-canvas @shopify/react-native-skia

or

npm install @duxapp/react-native-canvas @shopify/react-native-skia

Required dependency:

  • @shopify/react-native-skia

This package is pure JS, but @shopify/react-native-skia must be installed in the host project.

Quick Start

import { useEffect } from 'react'
import { Canvas, useCanvasRef } from '@duxapp/react-native-canvas'

export default function Demo() {
  const ref = useCanvasRef()

  useEffect(() => {
    let mounted = true

    ref.current?.getCanvas().then(({ canvas, size }) => {
      if (!mounted) return

      const ctx = canvas.getContext('2d')

      ctx.fillStyle = '#f5f5f5'
      ctx.fillRect(0, 0, size.width, size.height)

      ctx.beginPath()
      ctx.moveTo(20, 20)
      ctx.lineTo(180, 20)
      ctx.lineTo(100, 120)
      ctx.closePath()

      ctx.fillStyle = '#2f80ed'
      ctx.strokeStyle = '#0f4aa1'
      ctx.lineWidth = 4
      ctx.fill()
      ctx.stroke()

      ctx.font = 'bold 20px sans-serif'
      ctx.fillStyle = '#111'
      ctx.fillText('Hello Canvas', 20, 170)
    })

    return () => {
      mounted = false
    }
  }, [ref])

  return <Canvas ref={ref} style={{ flex: 1, minHeight: 240 }} />
}

Example

import { useEffect } from 'react'
import { Canvas, Path2D, useCanvasRef } from '@duxapp/react-native-canvas'

export default function Example() {
  const ref = useCanvasRef()

  useEffect(() => {
    ref.current?.getCanvas().then(({ canvas }) => {
      const ctx = canvas.getContext('2d')
      const path = new Path2D()

      path.moveTo(40, 40)
      path.lineTo(160, 40)
      path.lineTo(100, 140)
      path.closePath()

      ctx.fillStyle = '#e8f1ff'
      ctx.fillRect(0, 0, canvas.width, canvas.height)

      ctx.fillStyle = '#2f80ed'
      ctx.strokeStyle = '#1456b8'
      ctx.lineWidth = 3
      ctx.fill(path)
      ctx.stroke(path)
    })
  }, [ref])

  return <Canvas ref={ref} style={{ flex: 1 }} />
}

Picture Animation Example

import { useEffect } from 'react'
import { Canvas, useCanvasRef } from '@duxapp/react-native-canvas'

export default function PictureAnimationExample() {
  const ref = useCanvasRef()

  useEffect(() => {
    let frameId = 0
    let active = true

    ref.current?.getCanvas().then(({ canvas, size }) => {
      if (!active) return

      const ctx = canvas.getContext('2d')
      let x = 30
      let direction = 1

      const render = () => {
        if (!active) return

        ctx.fillStyle = '#f7f8fa'
        ctx.fillRect(0, 0, size.width, size.height)

        ctx.beginPath()
        ctx.arc(x, size.height / 2, 24, 0, Math.PI * 2)
        ctx.fillStyle = '#2f80ed'
        ctx.fill()

        x += direction * 2
        if (x >= size.width - 30 || x <= 30) {
          direction *= -1
        }

        frameId = requestAnimationFrame(render)
      }

      render()
    })

    return () => {
      active = false
      cancelAnimationFrame(frameId)
    }
  }, [ref])

  return <Canvas ref={ref} picture style={{ flex: 1, minHeight: 240 }} />
}

Performance

Current performance can be summarized like this:

  • compared with existing React Native canvas plugins such as react-native-canvas and react-native-gcanvas, this library usually delivers a very large performance improvement in the same type of drawing scenarios
  • compared with native canvas implementations, there is still a large gap
  • in the same scene, current performance is still generally well below half of native canvas performance

This means:

  • if you are moving from older React Native canvas plugins, the performance improvement is usually very noticeable
  • if your target is native canvas level performance, you should still expect a significant gap
  • enabling picture mode is strongly recommended when your rendering model allows it, because it helps maximize the current implementation's frame rate

Detailed Documentation

The sections below describe the package API, rendering modes, supported drawing sources, compatibility notes, and known limitations in detail.

Core Usage

1. Create a ref with useCanvasRef

const ref = useCanvasRef()

This ref is passed directly to the Canvas component:

<Canvas ref={ref} style={{ flex: 1 }} />

2. Get the canvas instance

const { canvas, size } = await ref.current.getCanvas()

Returned values:

  • canvas: canvas-like object
  • size: current layout info { width, height, x, y }

3. Get the 2D context

const ctx = canvas.getContext('2d')

Exports

import {
  Canvas,
  Image,
  OffscreenCanvas,
  Path2D,
  defineCanvas,
  defineCanvasContext,
  useCanvasRef,
  useClickable
} from '@duxapp/react-native-canvas'

Export Notes

  • Canvas: React Native canvas component
  • useCanvasRef: hook returning the ref used by Canvas
  • Path2D: path helper compatible with this canvas implementation
  • OffscreenCanvas: offscreen drawing surface
  • Image: image source object for drawImage()
  • useClickable: standalone hook that simulates web-style touch events on React Native
  • defineCanvas / defineCanvasContext: identity helpers kept for compatibility

useClickable

useClickable is a standalone hook extracted from the original RN implementation. Its purpose is to simulate web-style touch events on React Native so some libraries can run with fewer adaptations. It is exported for reuse with other components and is not wired into Canvas internally.

import { Canvas, useCanvasRef, useClickable } from '@duxapp/react-native-canvas'

export default function Demo() {
  const ref = useCanvasRef()
  const clickable = useClickable({
    onClick: e => {
      console.log('click', e.detail.x, e.detail.y)
    },
    onLongPress: e => {
      console.log('longpress', e)
    }
  })

  return <Canvas ref={ref} {...clickable} style={{ flex: 1, minHeight: 240 }} />
}

Supported callbacks:

  • onClick
  • onLongPress
  • onTouchStart
  • onTouchMove
  • onTouchEnd
  • onTouchCancel

API Overview

Canvas Component

Props:

  • style
  • onLayout
  • picture

picture enables the picture-based render path on React Native and is recommended in most cases.

Canvas Ref

ref.current.getCanvas(): Promise<{
  canvas: CanvasElement
  size: {
    width: number
    height: number
    x: number
    y: number
  }
}>

Canvas Element

The returned canvas supports:

  • getContext('2d')
  • createImageData(width, height)
  • toDataURL(type?, encoderOptions?)
  • width
  • height

Only getContext('2d') is supported. WebGL, WebGL2, and other 3D rendering contexts are not supported.

DPI Handling

Device pixel ratio is already handled internally.

Do not manually scale canvas size, coordinates, or transforms again with:

  • PixelRatio.get()
  • window.devicePixelRatio
  • custom DPR multipliers

In normal usage, just draw with logical layout units.

2D Context

Supported groups include:

  • state: save, restore
  • transforms: translate, scale, rotate, transform, setTransform, resetTransform, getTransform
  • path: beginPath, closePath, moveTo, lineTo, arc, arcTo, bezierCurveTo, quadraticCurveTo, rect, roundRect, ellipse
  • drawing: fill, stroke, clip, isPointInPath
  • rect: fillRect, strokeRect, clearRect
  • text: fillText, strokeText, measureText
  • image: drawImage
  • image data: getImageData, putImageData
  • styles: fillStyle, strokeStyle, lineWidth, lineCap, lineJoin, miterLimit, setLineDash, lineDashOffset
  • alpha and shadows: globalAlpha, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY
  • text styles: font, textAlign, textBaseline, direction
  • compositing: globalCompositeOperation
  • gradients and patterns: createLinearGradient, createRadialGradient, createPattern

Picture Mode

Enable picture mode like this:

<Canvas ref={ref} picture style={{ flex: 1 }} />

This mode can significantly improve drawing frame rate and is recommended by default unless you specifically depend on incremental canvas state behavior.

Recommended usage

  • enable it by default for most canvas rendering work
  • especially useful for charts, posters, previews, and other full redraw scenes
  • keep it enabled unless your rendering flow depends on incremental updates to a persistent canvas state

Default advantages

  • usually delivers higher frame rate
  • works especially well for animation and full-frame redraw
  • simpler rendering model when each frame is computed from scratch

Important limitations

  • picture rendering does not preserve drawing history
  • only the content drawn in the current frame is kept
  • when the next frame is drawn, the previous frame content is cleared automatically
  • drawing state is not preserved between frames
  • transform state is not preserved between frames
  • clearRect() does not behave like persistent incremental erase history

If your rendering depends on partial updates over previously drawn content, this mode is usually not the right choice.

Path2D

import { Path2D } from '@duxapp/react-native-canvas'

const path = new Path2D()
path.moveTo(20, 20)
path.lineTo(100, 20)
path.lineTo(60, 80)
path.closePath()

ctx.fill(path)
ctx.stroke(path)

You can also construct from another Path2D or from an SVG path string.

OffscreenCanvas

import { OffscreenCanvas } from '@duxapp/react-native-canvas'

const offscreen = new OffscreenCanvas(200, 120)
const ctx = offscreen.getContext('2d')

ctx.fillStyle = '#000'
ctx.fillRect(0, 0, 200, 120)

const dataUrl = offscreen.toDataURL()

Useful for:

  • pre-rendering
  • generating textures or thumbnails
  • drawing sources passed into drawImage()
  • patterns created with createPattern()

Image

import { Image } from '@duxapp/react-native-canvas'

const image = new Image()
image.onload = () => {
  ctx.drawImage(image, 0, 0, 120, 120)
}
image.onerror = err => {
  console.error(err)
}
image.src = 'https://example.com/example.png'

Supported src forms:

  • remote URL string
  • data: URL string
  • local asset id returned by require(...)

Supported members:

  • src
  • onload
  • onerror
  • onabort
  • alt
  • complete
  • currentSrc
  • width
  • height
  • decode()
  • addEventListener()
  • removeEventListener()

drawImage() Sources

Currently supported image sources in type definitions and runtime:

  • Image
  • OffscreenCanvas

Example:

const offscreen = new OffscreenCanvas(100, 100)
const offscreenCtx = offscreen.getContext('2d')
offscreenCtx.fillStyle = 'red'
offscreenCtx.fillRect(0, 0, 100, 100)

ctx.drawImage(offscreen, 20, 20)

toDataURL()

Both the main canvas object and OffscreenCanvas support toDataURL():

const url = canvas.toDataURL()
const jpeg = canvas.toDataURL('image/jpeg', 0.9)

Supported output types:

  • image/png
  • image/jpeg
  • image/webp

Compatibility Notes

This library is canvas-like, not a full browser HTMLCanvasElement implementation.

Some APIs intentionally differ from the browser:

  • no DOM APIs
  • no full browser image loading behavior
  • no browser event model
  • no mini program compatibility APIs in this package build

Some browser APIs are intentionally not exposed if they are not useful in React Native.

Known Limitations

  • behavior may differ slightly from browser canvas in edge cases
  • text measurement depends on Skia font behavior
  • image loading depends on the React Native / Skia environment
  • picture mode is optimized for full redraw, not incremental rendering
  • not every browser canvas API is implemented

Type Definitions

The package ships with TypeScript definitions:

  • default English comments: src/index.d.ts
  • retained Chinese version: src/index.zh-CN.d.ts

License

MIT

About

Pure JavaScript canvas-like API for React Native powered by react-native-skia

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors