├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── injectedHtml └── index.js ├── injectedJavaScript ├── application.js ├── errorHandler.js ├── executeNativeFunction.js └── signaturePad.js ├── package.json └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "env": { 5 | "browser": true, 6 | "jest": true 7 | }, 8 | "rules": { 9 | "object-curly-newline": 0, 10 | "object-curly-spacing": 0, 11 | "max-len": 0, 12 | "react/require-default-props": 0, 13 | "react/prop-types": 0, 14 | "react/jsx-tag-spacing": 0, 15 | "comma-dangle": 0, 16 | "no-use-before-define": 0, 17 | "import/prefer-default-export": 0, 18 | "prefer-promise-reject-errors": 0, 19 | "no-trailing-spaces": [0], 20 | "padded-blocks": [0], 21 | "no-restricted-globals": [0], 22 | "react/jsx-filename-extension": 0, 23 | "jsx-a11y/media-has-caption": 0, 24 | "jsx-a11y/anchor-is-valid": 0, 25 | "react/no-did-update-set-state": 0, 26 | "no-console": "off", 27 | "react/destructuring-assignment": [1, "always", { "ignoreClassFields": true }] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | .idea 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 DispatcherInc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-signature-pad 2 | 3 | [![npm version](https://badge.fury.io/js/react-native-signature-pad.svg)](//npmjs.com/package/react-native-signature-pad) 4 | [![star this repo](http://githubbadges.com/star.svg?user=kevinstumpf&repo=react-native-signature-pad&style=flat)](https://github.com/kevinstumpf/react-native-signature-pad) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors) [![Known Vulnerabilities](https://snyk.io/test/github/kevinstumpf/react-native-signature-pad/badge.svg?style=flat-square)](https://snyk.io/test/github/kevinstumpf/react-native-signature-pad) 5 | 6 | React Native wrapper around @[szimek's](https://github.com/szimek) HTML5 Canvas based [Signature Pad](https://github.com/szimek/signature_pad) 7 | 8 | - Supports Android and iOS 9 | - Pure JavaScript implementation with no native dependencies 10 | - Tested with RN 0.20 11 | - Can easily be rotated using the "transform" style 12 | - Generates a base64 encoded png image of the signature 13 | 14 | ## Demo 15 | 16 | ![SignaturePadDemo](https://cloud.githubusercontent.com/assets/7293984/13297035/303fefc6-dae5-11e5-99e8-edb8335633b5.gif) ![SignaturePadDemoAndroid](https://cloud.githubusercontent.com/assets/7293984/13299954/72bc3bf4-daf2-11e5-8606-388c05c26d6d.gif) 17 | 18 | ## Installation 19 | 20 | ```sh 21 | $ yarn add react-native-signature-pad 22 | ``` 23 | 24 | ## Example 25 | 26 | ```js 27 | import React, {Component} from 'react'; 28 | import {View} from 'react-native'; 29 | import SignaturePad from 'react-native-signature-pad'; 30 | 31 | export default class Demo extends Component { 32 | render = () => { 33 | return ( 34 | 35 | 38 | 39 | ) 40 | }; 41 | 42 | _signaturePadError = (error) => { 43 | console.error(error); 44 | }; 45 | 46 | _signaturePadChange = ({base64DataUrl}) => { 47 | console.log("Got new signature: " + base64DataUrl); 48 | }; 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {StyleSheet, View, ViewPropTypes} from 'react-native'; 4 | import {WebView} from 'react-native-webview'; 5 | 6 | 7 | import htmlContent from './injectedHtml'; 8 | import injectedSignaturePad from './injectedJavaScript/signaturePad'; 9 | import injectedApplication from './injectedJavaScript/application'; 10 | import injectedErrorHandler from './injectedJavaScript/errorHandler'; 11 | import injectedExecuteNativeFunction from './injectedJavaScript/executeNativeFunction'; 12 | 13 | export default class SignaturePad extends Component { 14 | 15 | static propTypes = { 16 | onChange: PropTypes.func, 17 | onError: PropTypes.func, 18 | style: ViewPropTypes.style, 19 | penColor: PropTypes.string, 20 | dataURL: PropTypes.string 21 | }; 22 | 23 | static defaultProps = { 24 | onChange: () => { 25 | }, 26 | onError: () => { 27 | 28 | }, 29 | style: {} 30 | }; 31 | 32 | constructor(props) { 33 | super(props); 34 | this.state = {base64DataUrl: props.dataURL || null}; 35 | const { backgroundColor } = StyleSheet.flatten(props.style); 36 | var injectedJavaScript = injectedExecuteNativeFunction 37 | + injectedErrorHandler 38 | + injectedSignaturePad 39 | + injectedApplication(props.penColor, backgroundColor, props.dataURL); 40 | var html = htmlContent(injectedJavaScript); 41 | this.source = {html}; //We don't use WebView's injectedJavaScript because on Android, the WebView re-injects the JavaScript upon every url change. Given that we use url changes to communicate signature changes to the React Native app, the JS is re-injected every time a stroke is drawn. 42 | } 43 | 44 | _onNavigationChange = (args) => { 45 | this._parseMessageFromWebViewNavigationChange(unescape(args.url)); 46 | }; 47 | 48 | _parseMessageFromWebViewNavigationChange = (newUrl) => { 49 | //Example input: 50 | //applewebdata://4985ECDA-4C2B-4E37-87ED-0070D14EB985#executeFunction=jsError&arguments=%7B%22message%22:%22ReferenceError:%20Can't%20find%20variable:%20WHADDUP%22,%22url%22:%22applewebdata://4985ECDA-4C2B-4E37-87ED-0070D14EB985%22,%22line%22:340,%22column%22:10%7D" 51 | //All parameters to the native world are passed via a hash url where every parameter is passed as &[ParameterName]<-[Content]& 52 | var hashUrlIndex = newUrl.lastIndexOf('#'); 53 | if(hashUrlIndex === -1) { 54 | return; 55 | } 56 | 57 | var hashUrl = newUrl.substring(hashUrlIndex); 58 | hashUrl = decodeURIComponent(hashUrl); 59 | var regexFindAllSubmittedParameters = /&(.*?)&/g; 60 | 61 | var parameters = {}; 62 | var parameterMatch = regexFindAllSubmittedParameters.exec(hashUrl); 63 | if(!parameterMatch) { 64 | return; 65 | } 66 | 67 | while(parameterMatch) { 68 | var parameterPair = parameterMatch[1]; //For example executeFunction=jsError or arguments=... 69 | 70 | var parameterPairSplit = parameterPair.split('<-'); 71 | if(parameterPairSplit.length === 2) { 72 | parameters[parameterPairSplit[0]] = parameterPairSplit[1]; 73 | } 74 | 75 | parameterMatch = regexFindAllSubmittedParameters.exec(hashUrl); 76 | } 77 | 78 | if(!this._attemptToExecuteNativeFunctionFromWebViewMessage(parameters)) { 79 | logger.warn({parameters, hashUrl}, 'Received an unknown set of parameters from WebView'); 80 | } 81 | }; 82 | 83 | _attemptToExecuteNativeFunctionFromWebViewMessage = (message) => { 84 | if(message.executeFunction && message.arguments) { 85 | var parsedArguments = JSON.parse(message.arguments); 86 | 87 | var referencedFunction = this['_bridged_' + message.executeFunction]; 88 | if(typeof(referencedFunction) === 'function') { 89 | referencedFunction.apply(this, [parsedArguments]); 90 | return true; 91 | } 92 | } 93 | 94 | return false; 95 | }; 96 | 97 | _bridged_jsError = (args) => { 98 | this.props.onError({details: args}); 99 | }; 100 | 101 | _bridged_finishedStroke = ({base64DataUrl}) => { 102 | this.props.onChange({base64DataUrl}); 103 | this.setState({base64DataUrl}); 104 | }; 105 | 106 | _renderError = (args) => { 107 | this.props.onError({details: args}); 108 | }; 109 | 110 | _renderLoading = (args) => { 111 | 112 | }; 113 | 114 | onMessage = (event) => { 115 | var base64DataUrl = JSON.parse(event.nativeEvent.data); 116 | this._bridged_finishedStroke(base64DataUrl); 117 | } 118 | 119 | render = () => { 120 | return ( 121 | 129 | ) 130 | }; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /injectedHtml/index.js: -------------------------------------------------------------------------------- 1 | var content = script => 2 | ` 3 | 4 | 22 | 23 | 24 | 27 | 28 | `; 29 | 30 | export default content; 31 | -------------------------------------------------------------------------------- /injectedJavaScript/application.js: -------------------------------------------------------------------------------- 1 | var content = (penColor, backgroundColor, dataURL) => ` 2 | 3 | var showSignaturePad = function (signaturePadCanvas, bodyWidth, bodyHeight) { 4 | /*We're rotating by 90% -> Flip X and Y*/ 5 | /*var width = bodyHeight; 6 | var height = bodyWidth;*/ 7 | 8 | var width = bodyWidth; 9 | var height = bodyHeight; 10 | 11 | var sizeSignaturePad = function () { 12 | var devicePixelRatio = 1; /*window.devicePixelRatio || 1;*/ 13 | var canvasWidth = width * devicePixelRatio; 14 | var canvasHeight = height * devicePixelRatio; 15 | signaturePadCanvas.width = canvasWidth; 16 | signaturePadCanvas.height = canvasHeight; 17 | signaturePadCanvas.getContext('2d').scale(devicePixelRatio, devicePixelRatio); 18 | }; 19 | 20 | var finishedStroke = function(base64DataUrl) { 21 | executeNativeFunction('finishedStroke', {base64DataUrl: base64DataUrl}); 22 | }; 23 | 24 | var enableSignaturePadFunctionality = function () { 25 | var signaturePad = new SignaturePad(signaturePadCanvas, { 26 | penColor: '${penColor || 'black'}', 27 | backgroundColor: '${backgroundColor || 'white'}', 28 | onEnd: function() { finishedStroke(signaturePad.toDataURL()); } 29 | }); 30 | /* signaturePad.translateMouseCoordinates = function (point) { 31 | var translatedY = point.x; 32 | var translatedX = width - point.y; 33 | point.x = translatedX; 34 | point.y = translatedY; 35 | }; */ 36 | signaturePad.minWidth = 1; 37 | signaturePad.maxWidth = 4; 38 | if ('${dataURL}') { 39 | signaturePad.fromDataURL('${dataURL}'); 40 | } 41 | }; 42 | 43 | sizeSignaturePad(); 44 | enableSignaturePadFunctionality(); 45 | }; 46 | 47 | 48 | var bodyWidth = window.innerWidth; 49 | var bodyHeight = window.innerHeight; 50 | 51 | var canvasElement = document.querySelector('canvas'); 52 | showSignaturePad(canvasElement, bodyWidth, bodyHeight); 53 | `; 54 | 55 | export default content; 56 | -------------------------------------------------------------------------------- /injectedJavaScript/errorHandler.js: -------------------------------------------------------------------------------- 1 | var content = ` 2 | window.onerror = function(message, url, line, column, error) { 3 | executeNativeFunction('jsError', {message: message, url: url, line: line, column: column}); 4 | }; 5 | `; 6 | 7 | export default content; -------------------------------------------------------------------------------- /injectedJavaScript/executeNativeFunction.js: -------------------------------------------------------------------------------- 1 | var content = ` 2 | function executeNativeFunction(fnName, args) { 3 | window.location.hash = '&executeFunction<-' + fnName + '&' + '&arguments<-' + JSON.stringify(args) + '&' + window.ReactNativeWebView.postMessage(JSON.stringify(args)) + '&'; 4 | } 5 | `; 6 | 7 | export default content; 8 | -------------------------------------------------------------------------------- /injectedJavaScript/signaturePad.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Signature Pad v1.3.2 3 | * https://github.com/szimek/signature_pad 4 | * 5 | * Copyright 2013 Szymon Nowak 6 | * Released under the MIT license 7 | * 8 | * The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from: 9 | * http://corner.squareup.com/2012/07/smoother-signatures.html 10 | * 11 | * Implementation of interpolation using cubic Bézier curves is taken from: 12 | * http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript 13 | * 14 | * Algorithm for approximated length of a Bézier curve is taken from: 15 | * http://www.lemoda.net/maths/bezier-length/index.html 16 | * 17 | */ 18 | var content = `var SignaturePad = (function (document) { 19 | "use strict"; 20 | 21 | var SignaturePad = function (canvas, options) { 22 | var self = this, 23 | opts = options || {}; 24 | 25 | this.velocityFilterWeight = opts.velocityFilterWeight || 0.7; 26 | this.minWidth = opts.minWidth || 0.5; 27 | this.maxWidth = opts.maxWidth || 2.5; 28 | this.dotSize = opts.dotSize || function () { 29 | return (this.minWidth + this.maxWidth) / 2; 30 | }; 31 | this.penColor = opts.penColor || "black"; 32 | this.backgroundColor = opts.backgroundColor || "rgba(0,0,0,0)"; 33 | this.onEnd = opts.onEnd; 34 | this.onBegin = opts.onBegin; 35 | 36 | this.pointWasOutOfCanvas = false; 37 | 38 | this._canvas = canvas; 39 | this._ctx = canvas.getContext("2d"); 40 | this.clear(); 41 | 42 | this._handleMouseEvents(); 43 | this._handleTouchEvents(); 44 | }; 45 | 46 | SignaturePad.prototype.clear = function () { 47 | var ctx = this._ctx, 48 | canvas = this._canvas; 49 | 50 | ctx.fillStyle = this.backgroundColor; 51 | ctx.clearRect(0, 0, canvas.width, canvas.height); 52 | ctx.fillRect(0, 0, canvas.width, canvas.height); 53 | this._reset(); 54 | }; 55 | 56 | SignaturePad.prototype.toDataURL = function (imageType, quality) { 57 | var canvas = this._canvas; 58 | return canvas.toDataURL.apply(canvas, arguments); 59 | }; 60 | 61 | SignaturePad.prototype.fromDataURL = function (dataUrl) { 62 | var self = this, 63 | image = new Image(); 64 | 65 | this._reset(); 66 | image.src = dataUrl; 67 | image.onload = function () { 68 | self._ctx.drawImage(image, 0, 0, self._canvas.width, self._canvas.height); 69 | }; 70 | this._isEmpty = false; 71 | }; 72 | 73 | SignaturePad.prototype._strokeUpdate = function (event) { 74 | var point = this._createPoint(event); 75 | this._addPoint(point); 76 | }; 77 | 78 | SignaturePad.prototype._strokeBegin = function (event) { 79 | this._reset(); 80 | this._strokeUpdate(event); 81 | if (typeof this.onBegin === 'function') { 82 | this.onBegin(event); 83 | } 84 | }; 85 | 86 | SignaturePad.prototype._strokeDraw = function (point) { 87 | var ctx = this._ctx, 88 | dotSize = typeof(this.dotSize) === 'function' ? this.dotSize() : this.dotSize; 89 | 90 | ctx.beginPath(); 91 | this._drawPoint(point.x, point.y, dotSize); 92 | ctx.closePath(); 93 | ctx.fill(); 94 | }; 95 | 96 | SignaturePad.prototype._strokeEnd = function (event) { 97 | var canDrawCurve = this.points.length > 2, 98 | point = this.points[0]; 99 | 100 | if (!canDrawCurve && point) { 101 | this._strokeDraw(point); 102 | } 103 | if (typeof this.onEnd === 'function') { 104 | this.onEnd(event); 105 | } 106 | }; 107 | 108 | SignaturePad.prototype._handleMouseEvents = function () { 109 | var self = this; 110 | this._mouseButtonDown = false; 111 | 112 | this._canvas.addEventListener("mousedown", function (event) { 113 | if (event.which === 1) { 114 | self._mouseButtonDown = true; 115 | self._strokeBegin(event); 116 | } 117 | }); 118 | 119 | this._canvas.addEventListener("mousemove", function (event) { 120 | if (self._mouseButtonDown) { 121 | self._strokeUpdate(event); 122 | } 123 | }); 124 | 125 | document.addEventListener("mouseup", function (event) { 126 | if (event.which === 1 && self._mouseButtonDown) { 127 | self._mouseButtonDown = false; 128 | self._strokeEnd(event); 129 | } 130 | }); 131 | }; 132 | 133 | SignaturePad.prototype._handleTouchEvents = function () { 134 | var self = this; 135 | 136 | /* Pass touch events to canvas element on mobile IE. */ 137 | this._canvas.style.msTouchAction = 'none'; 138 | 139 | this._canvas.addEventListener("touchstart", function (event) { 140 | var touch = event.changedTouches[0]; 141 | self._strokeBegin(touch); 142 | }); 143 | 144 | this._canvas.addEventListener("touchmove", function (event) { 145 | /* Prevent scrolling. */ 146 | event.preventDefault(); 147 | 148 | var touch = event.changedTouches[0]; 149 | var rect = self._canvas.getBoundingClientRect(); 150 | var x = touch.clientX - rect.left; 151 | var y = touch.clientY - rect.top; 152 | 153 | var pointOutOfCanvas = (x < 0 || y < 0 || x > rect.width || y > rect.height); // checking if point is outside of canvas 154 | if (pointOutOfCanvas) { 155 | if (!self.pointWasOutOfCanvas) { 156 | // if the previous point was inside of canvas and the new point is outside 157 | // call onEnd to capture the signature 158 | self.onEnd(); 159 | } 160 | // set the pointWasOutOfCanvas to true to indicate that user has drawn out of canvas 161 | self.pointWasOutOfCanvas = true; 162 | } else { 163 | // if the point is still inside canvas call _stokeUpdate as normal 164 | self.pointWasOutOfCanvas = false; 165 | self._strokeUpdate(touch); 166 | } 167 | }); 168 | 169 | document.addEventListener("touchend", function (event) { 170 | var wasCanvasTouched = event.target === self._canvas; 171 | if (wasCanvasTouched) { 172 | self._strokeEnd(event); 173 | } 174 | }); 175 | }; 176 | 177 | SignaturePad.prototype.isEmpty = function () { 178 | return this._isEmpty; 179 | }; 180 | 181 | SignaturePad.prototype._reset = function () { 182 | this.points = []; 183 | this._lastVelocity = 0; 184 | this._lastWidth = (this.minWidth + this.maxWidth) / 2; 185 | this._isEmpty = true; 186 | this._ctx.fillStyle = this.penColor; 187 | }; 188 | 189 | SignaturePad.prototype._createPoint = function (event) { 190 | var rect = this._canvas.getBoundingClientRect(); 191 | var point = new Point( 192 | event.clientX - rect.left, 193 | event.clientY - rect.top 194 | ); 195 | 196 | if(this.translateMouseCoordinates) 197 | this.translateMouseCoordinates(point); 198 | 199 | return point; 200 | }; 201 | 202 | SignaturePad.prototype._addPoint = function (point) { 203 | var points = this.points, 204 | c2, c3, 205 | curve, tmp; 206 | 207 | points.push(point); 208 | 209 | if (points.length > 2) { 210 | /* To reduce the initial lag make it work with 3 points 211 | by copying the first point to the beginning. */ 212 | if (points.length === 3) points.unshift(points[0]); 213 | 214 | tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]); 215 | c2 = tmp.c2; 216 | tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]); 217 | c3 = tmp.c1; 218 | curve = new Bezier(points[1], c2, c3, points[2]); 219 | this._addCurve(curve); 220 | 221 | /* Remove the first element from the list, 222 | so that we always have no more than 4 points in points array. */ 223 | points.shift(); 224 | } 225 | }; 226 | 227 | SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) { 228 | var dx1 = s1.x - s2.x, dy1 = s1.y - s2.y, 229 | dx2 = s2.x - s3.x, dy2 = s2.y - s3.y, 230 | 231 | m1 = {x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0}, 232 | m2 = {x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0}, 233 | 234 | l1 = Math.sqrt(dx1*dx1 + dy1*dy1), 235 | l2 = Math.sqrt(dx2*dx2 + dy2*dy2), 236 | 237 | dxm = (m1.x - m2.x), 238 | dym = (m1.y - m2.y), 239 | 240 | k = l2 / (l1 + l2), 241 | cm = {x: m2.x + dxm*k, y: m2.y + dym*k}, 242 | 243 | tx = s2.x - cm.x, 244 | ty = s2.y - cm.y; 245 | 246 | return { 247 | c1: new Point(m1.x + tx, m1.y + ty), 248 | c2: new Point(m2.x + tx, m2.y + ty) 249 | }; 250 | }; 251 | 252 | SignaturePad.prototype._addCurve = function (curve) { 253 | var startPoint = curve.startPoint, 254 | endPoint = curve.endPoint, 255 | velocity, newWidth; 256 | 257 | velocity = endPoint.velocityFrom(startPoint); 258 | velocity = this.velocityFilterWeight * velocity 259 | + (1 - this.velocityFilterWeight) * this._lastVelocity; 260 | 261 | newWidth = this._strokeWidth(velocity); 262 | this._drawCurve(curve, this._lastWidth, newWidth); 263 | 264 | this._lastVelocity = velocity; 265 | this._lastWidth = newWidth; 266 | }; 267 | 268 | SignaturePad.prototype._drawPoint = function (x, y, size) { 269 | var ctx = this._ctx; 270 | 271 | ctx.moveTo(x, y); 272 | ctx.arc(x, y, size, 0, 2 * Math.PI, false); 273 | this._isEmpty = false; 274 | }; 275 | 276 | SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) { 277 | var ctx = this._ctx, 278 | widthDelta = endWidth - startWidth, 279 | drawSteps, width, i, t, tt, ttt, u, uu, uuu, x, y; 280 | 281 | drawSteps = Math.floor(curve.length()); 282 | ctx.beginPath(); 283 | for (i = 0; i < drawSteps; i++) { 284 | /* Calculate the Bezier (x, y) coordinate for this step. */ 285 | t = i / drawSteps; 286 | tt = t * t; 287 | ttt = tt * t; 288 | u = 1 - t; 289 | uu = u * u; 290 | uuu = uu * u; 291 | 292 | x = uuu * curve.startPoint.x; 293 | x += 3 * uu * t * curve.control1.x; 294 | x += 3 * u * tt * curve.control2.x; 295 | x += ttt * curve.endPoint.x; 296 | 297 | y = uuu * curve.startPoint.y; 298 | y += 3 * uu * t * curve.control1.y; 299 | y += 3 * u * tt * curve.control2.y; 300 | y += ttt * curve.endPoint.y; 301 | 302 | width = startWidth + ttt * widthDelta; 303 | this._drawPoint(x, y, width); 304 | } 305 | ctx.closePath(); 306 | ctx.fill(); 307 | }; 308 | 309 | SignaturePad.prototype._strokeWidth = function (velocity) { 310 | return Math.max(this.maxWidth / (velocity + 1), this.minWidth); 311 | }; 312 | 313 | 314 | var Point = function (x, y, time) { 315 | this.x = x; 316 | this.y = y; 317 | this.time = time || new Date().getTime(); 318 | }; 319 | 320 | Point.prototype.velocityFrom = function (start) { 321 | return (this.time !== start.time) ? this.distanceTo(start) / (this.time - start.time) : 1; 322 | }; 323 | 324 | Point.prototype.distanceTo = function (start) { 325 | return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2)); 326 | }; 327 | 328 | var Bezier = function (startPoint, control1, control2, endPoint) { 329 | this.startPoint = startPoint; 330 | this.control1 = control1; 331 | this.control2 = control2; 332 | this.endPoint = endPoint; 333 | }; 334 | 335 | /* Returns approximated length. */ 336 | Bezier.prototype.length = function () { 337 | var steps = 10, 338 | length = 0, 339 | i, t, cx, cy, px, py, xdiff, ydiff; 340 | 341 | for (i = 0; i <= steps; i++) { 342 | t = i / steps; 343 | cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x); 344 | cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y); 345 | if (i > 0) { 346 | xdiff = cx - px; 347 | ydiff = cy - py; 348 | length += Math.sqrt(xdiff * xdiff + ydiff * ydiff); 349 | } 350 | px = cx; 351 | py = cy; 352 | } 353 | return length; 354 | }; 355 | 356 | Bezier.prototype._point = function (t, start, c1, c2, end) { 357 | return start * (1.0 - t) * (1.0 - t) * (1.0 - t) 358 | + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t 359 | + 3.0 * c2 * (1.0 - t) * t * t 360 | + end * t * t * t; 361 | }; 362 | 363 | return SignaturePad; 364 | })(document);`; 365 | 366 | export default content; 367 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-signature-pad", 3 | "version": "0.1.2", 4 | "description": "React Native wrapper around szimek's Canvas based Signature Pad", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/DispatcherInc/react-native-signature-pad.git" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "react-component", 16 | "ios", 17 | "android", 18 | "signature", 19 | "pad", 20 | "signaturepad", 21 | "canvas" 22 | ], 23 | "author": "Kevin Stumpf", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/DispatcherInc/react-native-signature-pad/issues" 27 | }, 28 | "homepage": "https://github.com/DispatcherInc/react-native-signature-pad#readme", 29 | "peerDependencies": { 30 | "react": "^16.8.4", 31 | "react-native": "^0.58.6", 32 | "react-native-webview": "^5.3.0" 33 | }, 34 | "dependencies": { 35 | "prop-types": "^15.6.0" 36 | }, 37 | "devDependencies": { 38 | "babel-eslint": "10.0.1", 39 | "eslint": "^5.15.1", 40 | "eslint-config-airbnb": "^17.1.0", 41 | "eslint-plugin-import": "^2.16.0", 42 | "eslint-plugin-jsx-a11y": "^6.2.1", 43 | "eslint-plugin-react": "^7.12.4" 44 | } 45 | } 46 | --------------------------------------------------------------------------------