@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.
- Key Features
- Installation
- Quick Start
- Core Usage
- Example
- Performance
- Detailed Documentation
- Exports
- API Overview
- Picture Mode
Path2DOffscreenCanvasImageuseClickabledrawImage()SourcestoDataURL()- Compatibility Notes
- Known Limitations
- Type Definitions
- License
- Canvas component for React Native
- Canvas-like 2D context API
- 2D rendering only, no WebGL / 3D context support
Path2DOffscreenCanvasImageuseClickable- text drawing and measuring
- transforms and save/restore state
- gradients, patterns, shadows, composite operations
toDataURL()- recommended
picturerendering mode for higher frame rate in most drawing scenarios - built-in DPI handling, so you should not apply extra PixelRatio / devicePixelRatio scaling yourself
yarn add @duxapp/react-native-canvas @shopify/react-native-skiaor
npm install @duxapp/react-native-canvas @shopify/react-native-skiaRequired dependency:
@shopify/react-native-skia
This package is pure JS, but @shopify/react-native-skia must be installed in the host project.
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 }} />
}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 }} />
}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 }} />
}Current performance can be summarized like this:
- compared with existing React Native canvas plugins such as
react-native-canvasandreact-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
picturemode is strongly recommended when your rendering model allows it, because it helps maximize the current implementation's frame rate
The sections below describe the package API, rendering modes, supported drawing sources, compatibility notes, and known limitations in detail.
const ref = useCanvasRef()This ref is passed directly to the Canvas component:
<Canvas ref={ref} style={{ flex: 1 }} />const { canvas, size } = await ref.current.getCanvas()Returned values:
canvas: canvas-like objectsize: current layout info{ width, height, x, y }
const ctx = canvas.getContext('2d')import {
Canvas,
Image,
OffscreenCanvas,
Path2D,
defineCanvas,
defineCanvasContext,
useCanvasRef,
useClickable
} from '@duxapp/react-native-canvas'Canvas: React Native canvas componentuseCanvasRef: hook returning the ref used byCanvasPath2D: path helper compatible with this canvas implementationOffscreenCanvas: offscreen drawing surfaceImage: image source object fordrawImage()useClickable: standalone hook that simulates web-style touch events on React NativedefineCanvas/defineCanvasContext: identity helpers kept for compatibility
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:
onClickonLongPressonTouchStartonTouchMoveonTouchEndonTouchCancel
Props:
styleonLayoutpicture
picture enables the picture-based render path on React Native and is recommended in most cases.
ref.current.getCanvas(): Promise<{
canvas: CanvasElement
size: {
width: number
height: number
x: number
y: number
}
}>The returned canvas supports:
getContext('2d')createImageData(width, height)toDataURL(type?, encoderOptions?)widthheight
Only getContext('2d') is supported. WebGL, WebGL2, and other 3D rendering contexts are not supported.
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.
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
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.
- 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
- usually delivers higher frame rate
- works especially well for animation and full-frame redraw
- simpler rendering model when each frame is computed from scratch
- 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.
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.
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()
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:
srconloadonerroronabortaltcompletecurrentSrcwidthheightdecode()addEventListener()removeEventListener()
Currently supported image sources in type definitions and runtime:
ImageOffscreenCanvas
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)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/pngimage/jpegimage/webp
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.
- 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
The package ships with TypeScript definitions:
- default English comments:
src/index.d.ts - retained Chinese version:
src/index.zh-CN.d.ts
MIT