├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.yml
│ └── config.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.js
├── example
├── public
│ └── index.html
└── src
│ ├── index.js
│ └── styles.module.css
├── jest.config.ts
├── package-lock.json
├── package.json
├── rollup.config.ts
├── src
└── index.tsx
├── test
├── fixtures.ts
└── index.spec.tsx
├── tsconfig.build.json
├── tsconfig.json
└── types
└── trim-canvas.d.ts
/.github/ISSUE_TEMPLATE/bug-report.yml:
--------------------------------------------------------------------------------
1 | name: Reproducible Bug Report
2 | description: If you found a bug within `react-signature-canvas` itself and have a minimal reproduction of it. For support requests, use StackOverflow.
3 | body:
4 | # larger description of what this template's intended usage is
5 | - type: markdown
6 | attributes:
7 | value: |
8 | This template is to report a reproducible bug within `react-signature-canvas` itself.
9 |
10 | Issues [should _not_](https://docs.github.com/en/get-started/using-github/communicating-on-github) be used for support requests -- use [StackOverflow](https://stackoverflow.com/search?q=react-signature-canvas) for that instead.
11 |
12 | This should _not_ be used for issues with the underlying `signature_pad` -- use [`signature_pad`'s issues](https://github.com/szimek/signature_pad/issues) for that instead.
13 |
14 | Before opening a new issue, please do a [search of existing issues](https://github.com/agilgur5/react-signature-canvas/issues?q=is%3Aissue).
15 | If a relevant open issue exists, you should :+1: upvote it instead.
16 | If a relevant closed issue exists, please follow the directions of the closing comments.
17 | Do not open duplicates of existing issues.
18 |
19 | # require that users have searched existing issues
20 | - type: checkboxes
21 | attributes:
22 | label: Have you searched the existing issues?
23 | description: Please search to see if an issue already exists for the problem you encountered
24 | options:
25 | - label: I have searched the existing issues and cannot find my problem
26 | required: true
27 |
28 | # require that users provide a minimal reproduction
29 | - type: input
30 | attributes:
31 | label: Provide a link to code that _minimally_ reproduces this bug
32 | description: |
33 | Link to a [minimal reproduction](https://stackoverflow.com/help/minimal-reproducible-example) via a public [CodeSandbox](https://codesandbox.io/s/github/agilgur5/react-signature-canvas/tree/codesandbox-example), StackBlitz project, or GitHub repository.
34 |
35 | _Skipping this or providing an invalid link may result in your issue being summarily closed._
36 | placeholder: 'https://codesandbox.io/p/sandbox/my-minimal-react-signature-canvas-bug-reproduction'
37 | validations:
38 | required: true
39 |
40 | # require that users provide their environment details
41 | - type: textarea
42 | attributes:
43 | label: Provide version numbers for your environment by running the below command
44 | description: npx envinfo --npmPackages react-signature-canvas,react,react-dom,typescript --npmGlobalPackages typescript --binaries --browsers --system os
45 | render: text # render as a ```text code block
46 | # example output to clue in user about what it should look like
47 | placeholder: |
48 | System:
49 | OS: macOS 14.5
50 | Binaries:
51 | Node: 22.14.0 - ~/.local/share/mise/installs/node/22.14.0/bin/node
52 | Yarn: 1.22.19 - /usr/local/bin/yarn
53 | npm: 10.9.2 - ~/.local/share/mise/installs/node/22.14.0/bin/npm
54 | Browsers:
55 | Chrome: 134.0.6998.166
56 | Safari: 17.5
57 | npmPackages:
58 | react: ^19.0.0 => 19.0.0
59 | react-dom: ^19.0.0 => 19.0.0
60 | react-signature-canvas: ^1.0.7 => 1.0.7
61 | typescript: ^4.6.3 => 4.6.4
62 | validations:
63 | required: true
64 |
65 | # describe the problem
66 | - type: textarea
67 | attributes:
68 | label: Describe the problem, how to reproduce it, and why you believe the behavior is a bug in this library
69 | description: What is the current behavior vs. the expected behavior?
70 | render: markdown # render directly as markdown
71 | # example output to clue in user about what it should look like
72 | placeholder: |
73 | In the provided reproduction, run `npm run typecheck`. This results in a TypeScript error: `Could not find a declaration file for module 'react-signature-canvas'`.
74 | As this library is natively written in TypeScript, I assumed that type declarations should be provided and that a TS build would succeed.
75 | validations:
76 | required: true
77 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: Search on StackOverflow
3 | url: https://stackoverflow.com/search?q=react-signature-canvas
4 | about: Use StackOverflow for support questions. Issues are for reproducible bug reports and feature requests.
5 | - name: Upstream `signature_pad`'s issues
6 | url: https://github.com/szimek/signature_pad/issues
7 | about: This library is a wrapper around `signature_pad`. If you have an with `signature_pad` itself (as opposed to this wrapper), please see its issue tracker.
8 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push, pull_request]
3 |
4 | permissions:
5 | contents: read
6 |
7 | jobs:
8 | ci:
9 | name: CI - Node ${{ matrix.node-version }}, ${{ matrix.os }}
10 |
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | node-version: [18.x, 20.x, 22.x] # LTS Node: https://nodejs.org/en/about/releases/
15 | os: [ubuntu-latest]
16 |
17 | steps:
18 | - name: Checkout repo
19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
20 | - name: Setup Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | cache: 'npm'
25 | - name: Install
26 | run: npm ci
27 |
28 | - name: Typecheck
29 | run: npm run tsc
30 | - name: Lint
31 | run: npm run lint
32 | - name: Build
33 | run: npm run build
34 |
35 | - name: Test w/ coverage report
36 | run: npm run test:coverage
37 | - name: Upload coverage report to Codecov
38 | uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0
39 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### custom ###
2 |
3 | # parcel cache
4 | .parcel-cache/
5 | # build output
6 | dist/
7 | # test coverage output
8 | coverage/
9 |
10 | ### Node ###
11 |
12 | # Logs
13 | logs
14 | *.log
15 | npm-debug.log*
16 |
17 | # Dependency directories
18 | node_modules/
19 |
20 | # Optional npm cache directory
21 | .npm
22 |
23 | # Optional REPL history
24 | .node_repl_history
25 |
26 | # Output of 'npm pack'
27 | *.tgz
28 |
29 | # dotenv environment variables file
30 | .env
31 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | The changelog is currently hosted on [the GitHub Releases page](https://github.com/agilgur5/react-signature-canvas/releases).
4 | It is currently mostly a summary and list of commits made before any tag.
5 | The commits in this library mostly follow a convention and tend to be quite detailed.
6 |
7 | This project adheres to [Semantic Versioning](http://semver.org/).
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Anton Gilgur
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | # react-signature-canvas
7 |
8 |
9 | [](https://npmjs.org/package/react-signature-canvas)
10 | [](https://github.com/agilgur5/react-signature-canvas/releases)
11 | [](https://github.com/agilgur5/react-signature-canvas/commits/main)
12 |
13 | [](https://npmjs.org/package/react-signature-canvas)
14 | [](https://npmjs.org/package/react-signature-canvas)
15 | [](https://npmjs.org/package/react-signature-canvas)
16 | [](https://npmjs.org/package/react-signature-canvas)
17 |
18 | [](src/index.tsx)
19 | [](https://github.com/agilgur5/react-signature-canvas/actions/workflows/ci.yml?query=branch%3Amain)
20 | [](https://codecov.io/gh/agilgur5/react-signature-canvas)
21 |
22 | A React wrapper component around [signature_pad](https://github.com/szimek/signature_pad).
23 |
24 | Originally, this was just an _unopinionated_ fork of [react-signature-pad](https://github.com/blackjk3/react-signature-pad) that did not impose any styling or wrap any other unwanted elements around your canvas -- it's just a wrapper around a single canvas element!
25 | Hence the naming difference.
26 | Nowadays, this repo / library has significantly evolved, introducing new features, fixing various bugs, and now wrapping the upstream `signature_pad` to have its updates and bugfixes baked in.
27 |
28 | This fork also allows you to directly pass [props](#props) to the underlying canvas element, has new, documented [API methods](#api) you can use, has new, documented [props](#props) you can pass to it, has a [live demo](https://agilgur5.github.io/react-signature-canvas/), has a [CodeSandbox playground](https://codesandbox.io/s/github/agilgur5/react-signature-canvas/tree/codesandbox-example), has [100% test coverage](https://codecov.io/gh/agilgur5/react-signature-canvas), and is [written in TypeScript](src/index.tsx).
29 |
30 | ## Installation
31 |
32 | ```sh
33 | npm i -S react-signature-canvas
34 | ```
35 |
36 | ## Usage
37 |
38 | ```jsx
39 | import React from 'react'
40 | import { createRoot } from 'react-dom/client'
41 | import SignatureCanvas from 'react-signature-canvas'
42 |
43 | createRoot(
44 | document.getElementById('my-react-container')
45 | ).render(
46 | ,
48 | )
49 | ```
50 |
51 | ### Props
52 |
53 | The props of SignatureCanvas mainly control the properties of the pen stroke used in drawing.
54 | All props are **optional**.
55 |
56 | - `velocityFilterWeight` : `number`, default: `0.7`
57 | - `minWidth` : `number`, default: `0.5`
58 | - `maxWidth` : `number`, default: `2.5`
59 | - `minDistance`: `number`, default: `5`
60 | - `dotSize` : `number` or `function`,
61 | default: `() => (this.minWidth + this.maxWidth) / 2`
62 | - `penColor` : `string`, default: `'black'`
63 | - `throttle`: `number`, default: `16`
64 |
65 | There are also two callbacks that will be called when a stroke ends and one begins, respectively.
66 |
67 | - `onEnd` : `function`
68 | - `onBegin` : `function`
69 |
70 | Additional props are used to control the canvas element.
71 |
72 | - `canvasProps`: `object`
73 | - directly passed to the underlying `` element
74 | - `backgroundColor` : `string`, default: `'rgba(0,0,0,0)'`
75 | - used in the [API's](#api) `clear` convenience method (which itself is called internally during resizes)
76 | - `clearOnResize`: `bool`, default: `true`
77 | - whether or not the canvas should be cleared when the window resizes
78 |
79 | Of these props, all, except for `canvasProps` and `clearOnResize`, are passed through to `signature_pad` as its [options](https://github.com/szimek/signature_pad#options).
80 | `signature_pad`'s internal state is automatically kept in sync with prop updates for you (via a `componentDidUpdate` hook).
81 |
82 | ### API
83 |
84 | All API methods require [a ref](https://react.dev/learn/manipulating-the-dom-with-refs) to the SignatureCanvas in order to use and are instance methods of the ref.
85 |
86 | ```jsx
87 | import React, { useRef } from 'react'
88 | import SignatureCanvas from 'react-signature-canvas'
89 |
90 | function MyApp() {
91 | const sigCanvas = useRef(null);
92 |
93 | return
94 | }
95 | ```
96 |
97 | - `isEmpty()` : `boolean`, self-explanatory
98 | - `clear()` : `void`, clears the canvas using the `backgroundColor` prop
99 | - `fromDataURL(base64String, options)` : `void`, writes a base64 image to canvas
100 | - `toDataURL(mimetype, encoderOptions)`: `base64string`, returns the signature image as a data URL
101 | - `fromData(pointGroupArray)`: `void`, draws signature image from an array of point groups
102 | - `toData()`: `pointGroupArray`, returns signature image as an array of point groups
103 | - `off()`: `void`, unbinds all event handlers
104 | - `on()`: `void`, rebinds all event handlers
105 | - `getCanvas()`: `canvas`, returns the underlying canvas ref.
106 | Allows you to modify the canvas however you want or call methods such as `toDataURL()`
107 | - `getTrimmedCanvas()`: `canvas`, creates a copy of the canvas and returns a [trimmed version](https://github.com/agilgur5/trim-canvas) of it, with all whitespace removed.
108 | - `getSignaturePad()`: `SignaturePad`, returns the underlying SignaturePad reference.
109 |
110 | The API methods are _mostly_ just wrappers around [`signature_pad`'s API](https://github.com/szimek/signature_pad#api).
111 | `on()` and `off()` will, in addition, bind/unbind the window resize event handler.
112 | `getCanvas()`, `getTrimmedCanvas()`, and `getSignaturePad()` are new.
113 |
114 | ## Example
115 |
116 | You can interact with the example in a few different ways:
117 |
118 | 1. Run `npm start` and navigate to [http://localhost:1234/](http://localhost:1234/).
119 | Hosted locally via the [`example/`](example/) directory
120 |
121 | 1. [View the live demo here](https://agilgur5.github.io/react-signature-canvas/).
122 | Hosted via the [`gh-pages` branch](https://github.com/agilgur5/react-signature-canvas/tree/gh-pages), a standalone version of the code in [`example/`](example/)
123 |
124 | 1. [Play with the CodeSandbox here](https://codesandbox.io/s/github/agilgur5/react-signature-canvas/tree/codesandbox-example).
125 | Hosted via the [`codesandbox-example` branch](https://github.com/agilgur5/react-signature-canvas/tree/codesandbox-example), a slightly modified version of the above.
126 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const pkgJson = require('./package.json')
2 |
3 | const runtimeVersion = pkgJson.dependencies['@babel/runtime']
4 | // eslint-disable-next-line dot-notation -- this conflicts with tsc, possibly due to outdated ESLint
5 | const NODE_ENV = process.env['NODE_ENV']
6 |
7 | /** @type {import('@babel/core').ConfigFunction} */
8 | module.exports = api => {
9 | api.cache.using(() => NODE_ENV + '_' + runtimeVersion) // cache based on NODE_ENV and runtimeVersion
10 |
11 | // normally use browserslistrc, but for Jest, use current version of Node
12 | const isTest = api.env('test')
13 | const jestTargets = { targets: { node: 'current' } }
14 | /** @type {[import('@babel/core').PluginTarget, import('@babel/core').PluginOptions]} */
15 | const presetEnv = ['@babel/preset-env', { bugfixes: true }]
16 | if (isTest) presetEnv[1] = { ...presetEnv[1], ...jestTargets }
17 |
18 | return {
19 | // @ts-expect-error -- @types/babel__core doesn't specify assumptions yet
20 | assumptions: {
21 | // optimizations equivalent to previous Babel 6 "loose" behavior for preset-stage-2 (https://github.com/babel/rfcs/blob/main/rfcs/0003-top-level-assumptions.md#assumptions-list, https://github.com/babel/babel/tree/v7.5.5/packages/babel-preset-stage-2)
22 | setPublicClassFields: true,
23 | constantSuper: true
24 | },
25 | presets: [
26 | presetEnv,
27 | '@babel/preset-typescript',
28 | '@babel/preset-react'
29 | ],
30 | plugins: [
31 | // used with @rollup/plugin-babel
32 | ['@babel/plugin-transform-runtime', {
33 | regenerator: false, // not used, and would prefer babel-polyfills over this anyway
34 | version: runtimeVersion // @babel/runtime's version
35 | }]
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Signature Pad Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from 'react'
2 | import { createRoot } from 'react-dom/client'
3 |
4 | import SignatureCanvas from '../../src/index.tsx'
5 |
6 | import * as styles from './styles.module.css'
7 |
8 | function App () {
9 | const sigCanvas = useRef(null)
10 | const [trimmedDataURL, setTrimmedDataURL] = useState(null)
11 |
12 | function clear () {
13 | sigCanvas.current.clear()
14 | }
15 |
16 | function trim () {
17 | setTrimmedDataURL(sigCanvas.current.getTrimmedCanvas().toDataURL('image/png'))
18 | }
19 |
20 | return (
21 |