Cross-platform React Native drawing component based on SVG
npm install @benjeau/react-native-draw
# or
yarn add @benjeau/react-native-draw
Also, you need to install react-native-gesture-handler and react-native-svg, and follow their installation instructions.
Supporting components, such as CanvasControls
, ColorPicker
and BrushProperties
components, are available as a separate package, @benjeau/react-native-draw-extras
All the following examples are also available in the example Expo application
Here's the most simple example:
import React from 'react';
import { Canvas } from '@benjeau/react-native-draw';
export default () => <Canvas />;
Demo.-.Simple.Example.mp4
Here's a more complex example:
Complex example - Code snippet
import React, { useRef } from 'react';
import { Button } from 'react-native';
import { Canvas, CanvasRef } from '@benjeau/react-native-draw';
export default () => {
const canvasRef = useRef<CanvasRef>(null);
const handleUndo = () => {
canvasRef.current?.undo();
};
const handleClear = () => {
canvasRef.current?.clear();
};
return (
<>
<Canvas
ref={canvasRef}
height={600}
color="red"
thickness={20}
opacity={0.6}
style={{ backgroundColor: 'black' }}
/>
<Button title="Undo" onPress={handleUndo} />
<Button title="Clear" onPress={handleClear} />
</>
);
};
Demo.-.Complex.Example.mp4
This uses the @benjeau/react-native-draw-extras
npm package for the color picker and the bottom buttons/brush preview.
As this package does not depend on
@BenJeau/react-native-draw-extras
, it is completely optional and you can build your own supporting UI, just like the previous example
Extras example - Code snippet
import React, { useRef, useState } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import {
BrushProperties,
Canvas,
CanvasControls,
CanvasRef,
DEFAULT_COLORS,
DrawingTool,
} from '@benjeau/react-native-draw';
export default () => {
const canvasRef = useRef<CanvasRef>(null);
const [color, setColor] = useState(DEFAULT_COLORS[0][0][0]);
const [thickness, setThickness] = useState(5);
const [opacity, setOpacity] = useState(1);
const [tool, setTool] = useState(DrawingTool.Brush);
const [visibleBrushProperties, setVisibleBrushProperties] = useState(false);
const handleUndo = () => {
canvasRef.current?.undo();
};
const handleClear = () => {
canvasRef.current?.clear();
};
const handleToggleEraser = () => {
setTool((prev) =>
prev === DrawingTool.Brush ? DrawingTool.Eraser : DrawingTool.Brush
);
};
const [overlayOpacity] = useState(new Animated.Value(0));
const handleToggleBrushProperties = () => {
if (!visibleBrushProperties) {
setVisibleBrushProperties(true);
Animated.timing(overlayOpacity, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}).start();
} else {
Animated.timing(overlayOpacity, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}).start(() => {
setVisibleBrushProperties(false);
});
}
};
return (
<>
<Canvas
ref={canvasRef}
height={600}
color={color}
thickness={thickness}
opacity={opacity}
tool={tool}
style={{
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: '#ccc',
}}
/>
<View>
<CanvasControls
onUndo={handleUndo}
onClear={handleClear}
onToggleEraser={handleToggleEraser}
onToggleBrushProperties={handleToggleBrushProperties}
tool={tool}
color={color}
opacity={opacity}
thickness={thickness}
/>
{visibleBrushProperties && (
<BrushProperties
color={color}
thickness={thickness}
opacity={opacity}
onColorChange={setColor}
onThicknessChange={setThickness}
onOpacityChange={setOpacity}
style={{
position: 'absolute',
bottom: 80,
left: 0,
right: 0,
padding: 10,
backgroundColor: '#f2f2f2',
borderTopEndRadius: 10,
borderTopStartRadius: 10,
borderWidth: StyleSheet.hairlineWidth,
borderBottomWidth: 0,
borderTopColor: '#ccc',
opacity: overlayOpacity,
}}
/>
)}
</View>
</>
);
};
Demo.-.Extras.Example.mp4
name | description | type | default |
---|---|---|---|
color |
Color of the brush strokes | string |
- (required) |
thickness |
Thickness of the brush strokes | number |
- (required) |
opacity |
Opacity of the brush strokes | number |
- (required) |
initialPaths |
Paths to be already drawn | PathType[] |
[] |
height |
Height of the canvas | number |
height of the window - 80 |
width |
Width of the canvas | number |
width of the window |
style |
Override the style of the container of the canvas | StyleProp |
- |
onPathsChange |
Callback function when paths change | (paths: PathType []) => any |
- |
simplifyOptions |
SVG simplification options | SimplifyOptions |
see below |
eraserSize |
Width of eraser (to compensate for path simplification) | number |
5 |
tool |
Initial tool of the canvas | brush or eraser |
brush |
combineWithLatestPath |
Combine current path with the last path if it's the same color, thickness, and opacity | boolean |
false |
enabled |
Allows for the canvas to be drawn on, put to false if you want to disable/lock the canvas | boolean |
true |
name | description | type | default |
---|---|---|---|
simplifyPaths |
Enable SVG path simplification on paths, except the one currently being drawn | boolean |
true |
simplifyCurrentPath |
Enable SVG path simplification on the stroke being drawn | boolean |
false |
amount |
Amount of simplification to apply | number |
10 |
roundPoints |
Ignore fractional part in the points. Improves performance | boolean |
true |
name | description | type |
---|---|---|
undo |
Undo last brush stroke | () => void |
clear |
Removes all brush strokes | () => void |
getPaths |
Get brush strokes data | () => PathType[] |
addPath |
Append a path to the current drawing paths | (path: PathType) => void |
getSvg |
Get SVG path string of the drawing | () => string |
If you cannot draw on the canvas, make sure you have followed the extra steps of react-native-gesture-handler
- If you need to create an SVG path,
createSVGPath()
is available to create the string representation of an SVG path.
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT