UNPKG

react-canvas-draw

Version:

A simple yet powerful canvas-drawing component for React.

541 lines (413 loc) 15.7 kB
"use strict"; exports.__esModule = true; exports.clientPointFromEvent = clientPointFromEvent; exports.viewPointFromEvent = viewPointFromEvent; exports.SyntheticEvent = exports.DrawingState = exports.TouchScaleState = exports.TouchPanState = exports.ScaleOrPanState = exports.WaitForPinchState = exports.PanState = exports.DisabledState = exports.DefaultState = void 0; var _this = void 0; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var TOUCH_SLOP = 10; var PINCH_TIMEOUT_MS = 250; var SUPPRESS_SCROLL = function SUPPRESS_SCROLL(e) { // No zooming while drawing, but we'll cancel the scroll event. e.preventDefault(); return _this; }; /** * The default state for the interaction state machine. Supports zoom and * initiating pan and drawing actions. */ var DefaultState = function DefaultState() { var _this2 = this; _defineProperty(this, "handleMouseWheel", function (e, canvasDraw) { var _canvasDraw$props = canvasDraw.props, disabled = _canvasDraw$props.disabled, enablePanAndZoom = _canvasDraw$props.enablePanAndZoom, mouseZoomFactor = _canvasDraw$props.mouseZoomFactor; if (disabled) { return new DisabledState(); } else if (enablePanAndZoom && e.ctrlKey) { e.preventDefault(); canvasDraw.coordSystem.scaleAtClientPoint(mouseZoomFactor * e.deltaY, clientPointFromEvent(e)); } return _this2; }); _defineProperty(this, "handleDrawStart", function (e, canvasDraw) { if (canvasDraw.props.disabled) { return new DisabledState(); } else if (e.ctrlKey && canvasDraw.props.enablePanAndZoom) { return new PanState().handleDrawStart(e, canvasDraw); } else { return new WaitForPinchState().handleDrawStart(e, canvasDraw); } }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { if (canvasDraw.props.disabled) { return new DisabledState(); } else { var _viewPointFromEvent = viewPointFromEvent(canvasDraw.coordSystem, e), x = _viewPointFromEvent.x, y = _viewPointFromEvent.y; canvasDraw.lazy.update({ x: x, y: y }); return _this2; } }); _defineProperty(this, "handleDrawEnd", function (e, canvasDraw) { return canvasDraw.props.disabled ? new DisabledState() : _this2; }); }; exports.DefaultState = DefaultState; ; /** * This state is used as long as the disabled prop is active. It ignores all * events and doesn't prevent default actions. The disabled state can only be * triggered from the default state (i.e., while no action is actively being * performed). */ var DisabledState = function DisabledState() { var _this3 = this; _defineProperty(this, "handleMouseWheel", function (e, canvasDraw) { if (canvasDraw.props.disabled) { return _this3; } else { return new DefaultState().handleMouseWheel(e, canvasDraw); } }); _defineProperty(this, "handleDrawStart", function (e, canvasDraw) { if (canvasDraw.props.disabled) { return _this3; } else { return new DefaultState().handleDrawStart(e, canvasDraw); } }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { if (canvasDraw.props.disabled) { return _this3; } else { return new DefaultState().handleDrawMove(e, canvasDraw); } }); _defineProperty(this, "handleDrawEnd", function (e, canvasDraw) { if (canvasDraw.props.disabled) { return _this3; } else { return new DefaultState().handleDrawEnd(e, canvasDraw); } }); }; /** * This state is active as long as the user is panning the image. This state is * retained until the pan ceases. */ exports.DisabledState = DisabledState; var PanState = function PanState() { var _this4 = this; _defineProperty(this, "handleMouseWheel", SUPPRESS_SCROLL.bind(this)); _defineProperty(this, "handleDrawStart", function (e, canvasDraw) { e.preventDefault(); _this4.dragStart = clientPointFromEvent(e); _this4.panStart = { x: canvasDraw.coordSystem.x, y: canvasDraw.coordSystem.y }; return _this4; }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { e.preventDefault(); var _clientPointFromEvent = clientPointFromEvent(e), clientX = _clientPointFromEvent.clientX, clientY = _clientPointFromEvent.clientY; var dx = clientX - _this4.dragStart.clientX; var dy = clientY - _this4.dragStart.clientY; canvasDraw.coordSystem.setView({ x: _this4.panStart.x + dx, y: _this4.panStart.y + dy }); return _this4; }); _defineProperty(this, "handleDrawEnd", function () { return new DefaultState(); }); }; /** * This state is active when the user has initiated the drawing action but has * not yet created any lines. We use this state to try and detect a second touch * event to initiate a pinch-zoom action. We'll give up on that if enough time * or movement happens without a second touch. */ exports.PanState = PanState; var WaitForPinchState = function WaitForPinchState() { var _this5 = this; _defineProperty(this, "handleMouseWheel", SUPPRESS_SCROLL.bind(this)); _defineProperty(this, "handleDrawStart", function (e, canvasDraw) { var enablePanAndZoom = canvasDraw.props.enablePanAndZoom; e.preventDefault(); // We're going to transition immediately into lazy-drawing mode if // pan-and-zoom isn't enabled or if this event wasn't triggered by a touch. if (!e.touches || !e.touches.length || !enablePanAndZoom) { return new DrawingState().handleDrawStart(e, canvasDraw); } // If we already have two touch events, we can move straight into pinch/pan if (enablePanAndZoom && e.touches && e.touches.length >= 2) { return new ScaleOrPanState().handleDrawStart(e, canvasDraw); } return _this5.handleDrawMove(e, canvasDraw); }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { e.preventDefault(); // If we have two touches, move to pinch/pan (we don't have to recheck // whether zoom is enabled because that happend in draw start). if (e.touches && e.touches.length >= 2) { // Use the start draw to handler to transition. return new ScaleOrPanState().handleDrawStart(e, canvasDraw); } var clientPt = clientPointFromEvent(e); _this5.deferredPoints.push(clientPt); // If we've already moved far enough, or if enough time has passed, give up // and switch over to drawing. if (new Date().valueOf() - _this5.startTimestamp < PINCH_TIMEOUT_MS) { if (_this5.startClientPoint === null) { _this5.startClientPoint = clientPt; } // Note that we're using "manhattan distance" rather than computing a // hypotenuse here as a cheap approximation var d = Math.abs(clientPt.clientX - _this5.startClientPoint.clientX) + Math.abs(clientPt.clientY - _this5.startClientPoint.clientY); if (d < TOUCH_SLOP) { // We're not ready to give up yet. return _this5; } } // Okay, give up and start drawing. return _this5.issueDeferredPoints(canvasDraw); }); _defineProperty(this, "handleDrawEnd", function (e, canvasDraw) { // The user stopped drawing before we decided what to do. Just treat this as // if they were drawing all along. return _this5.issueDeferredPoints(canvasDraw).handleDrawEnd(e, canvasDraw); }); _defineProperty(this, "issueDeferredPoints", function (canvasDraw) { // Time to give up. Play our deferred points out to the drawing state. // The first point will have been a start draw. var nextState = new DrawingState(); for (var i = 0; i < _this5.deferredPoints.length; i++) { var deferredPt = _this5.deferredPoints[i]; var syntheticEvt = new SyntheticEvent(deferredPt); var func = i === 0 ? nextState.handleDrawStart : nextState.handleDrawMove; nextState = func(syntheticEvt, canvasDraw); } return nextState; }); this.startClientPoint = null; this.startTimestamp = new Date().valueOf(); this.deferredPoints = []; }; /** * This state is active when the user has added at least two touch points but we * don't yet know if they intend to pan or zoom. */ exports.WaitForPinchState = WaitForPinchState; var ScaleOrPanState = function ScaleOrPanState() { var _this6 = this; _defineProperty(this, "handleMouseWheel", SUPPRESS_SCROLL.bind(this)); _defineProperty(this, "handleDrawStart", function (e, canvasDraw) { e.preventDefault(); if (!e.touches || e.touches.length < 2) { return new DefaultState(); } _this6.start = _this6.getTouchMetrics(e); _this6.panStart = { x: canvasDraw.coordSystem.x, y: canvasDraw.coordSystem.y }; _this6.scaleStart = canvasDraw.coordSystem.scale; return _this6; }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { e.preventDefault(); if (!e.touches || e.touches.length < 2) { return new DefaultState(); } var _this6$recentMetrics = _this6.recentMetrics = _this6.getTouchMetrics(e), centroid = _this6$recentMetrics.centroid, distance = _this6$recentMetrics.distance; // Switch to scaling? var dd = Math.abs(distance - _this6.start.distance); if (dd >= TOUCH_SLOP) { return new TouchScaleState(_this6).handleDrawMove(e, canvasDraw); } // Switch to panning? var dx = centroid.clientX - _this6.start.centroid.clientX; var dy = centroid.clientY - _this6.start.centroid.clientY; var dc = Math.abs(dx) + Math.abs(dy); if (dc >= TOUCH_SLOP) { return new TouchPanState(_this6).handleDrawMove(e, canvasDraw); } // Not enough movement yet return _this6; }); _defineProperty(this, "handleDrawEnd", function () { return new DefaultState(); }); _defineProperty(this, "getTouchMetrics", function (e) { var _clientPointFromEvent2 = clientPointFromEvent(e.touches[0]), t1x = _clientPointFromEvent2.clientX, t1y = _clientPointFromEvent2.clientY; var _clientPointFromEvent3 = clientPointFromEvent(e.touches[1]), t2x = _clientPointFromEvent3.clientX, t2y = _clientPointFromEvent3.clientY; var dx = t2x - t1x; var dy = t2y - t1y; return { t1: { clientX: t1x, clientY: t1y }, t2: { clientX: t2x, clientY: t2y }, distance: Math.sqrt(dx * dx + dy * dy), centroid: { clientX: (t1x + t2x) / 2.0, clientY: (t1y + t2y) / 2.0 } }; }); }; /** * The user is actively using touch gestures to pan the image. */ exports.ScaleOrPanState = ScaleOrPanState; var TouchPanState = function TouchPanState(scaleOrPanState) { var _this7 = this; _defineProperty(this, "handleMouseWheel", SUPPRESS_SCROLL.bind(this)); _defineProperty(this, "handleDrawStart", function () { return _this7; }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { e.preventDefault(); if (!e.touches || e.touches.length < 2) { return new DefaultState(); } var ref = _this7.scaleOrPanState; var _ref$recentMetrics = ref.recentMetrics = ref.getTouchMetrics(e), centroid = _ref$recentMetrics.centroid, distance = _ref$recentMetrics.distance; var dx = centroid.clientX - ref.start.centroid.clientX; var dy = centroid.clientY - ref.start.centroid.clientY; canvasDraw.setView({ x: ref.panStart.x + dx, y: ref.panStart.y + dy }); return _this7; }); _defineProperty(this, "handleDrawEnd", function () { return new DefaultState(); }); this.scaleOrPanState = scaleOrPanState; }; /** * The user is actively using touch gestures to scale the drawing. */ exports.TouchPanState = TouchPanState; var TouchScaleState = function TouchScaleState(scaleOrPanState) { var _this8 = this; _defineProperty(this, "handleMouseWheel", SUPPRESS_SCROLL.bind(this)); _defineProperty(this, "handleDrawStart", function () { return _this8; }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { e.preventDefault(); if (!e.touches || e.touches.length < 2) { return new DefaultState(); } var ref = _this8.scaleOrPanState; var _ref$recentMetrics2 = ref.recentMetrics = ref.getTouchMetrics(e), centroid = _ref$recentMetrics2.centroid, distance = _ref$recentMetrics2.distance; var targetScale = ref.scaleStart * (distance / ref.start.distance); var dScale = targetScale - canvasDraw.coordSystem.scale; canvasDraw.coordSystem.scaleAtClientPoint(dScale, centroid); return _this8; }); _defineProperty(this, "handleDrawEnd", function () { return new DefaultState(); }); this.scaleOrPanState = scaleOrPanState; }; /** * This state is active when the user is drawing. */ exports.TouchScaleState = TouchScaleState; var DrawingState = function DrawingState() { var _this9 = this; _defineProperty(this, "handleMouseWheel", SUPPRESS_SCROLL.bind(this)); _defineProperty(this, "handleDrawStart", function (e, canvasDraw) { e.preventDefault(); if (e.touches && e.touches.length) { // on touch, set catenary position to touch pos var _viewPointFromEvent2 = viewPointFromEvent(canvasDraw.coordSystem, e), x = _viewPointFromEvent2.x, y = _viewPointFromEvent2.y; canvasDraw.lazy.update({ x: x, y: y }, { both: true }); } return _this9.handleDrawMove(e, canvasDraw); }); _defineProperty(this, "handleDrawMove", function (e, canvasDraw) { e.preventDefault(); var _viewPointFromEvent3 = viewPointFromEvent(canvasDraw.coordSystem, e), x = _viewPointFromEvent3.x, y = _viewPointFromEvent3.y; canvasDraw.lazy.update({ x: x, y: y }); var isDisabled = !canvasDraw.lazy.isEnabled(); if (!_this9.isDrawing || isDisabled) { // Start drawing and add point canvasDraw.points.push(canvasDraw.clampPointToDocument(canvasDraw.lazy.brush.toObject())); _this9.isDrawing = true; } // Add new point canvasDraw.points.push(canvasDraw.clampPointToDocument(canvasDraw.lazy.brush.toObject())); // Draw current points canvasDraw.drawPoints({ points: canvasDraw.points, brushColor: canvasDraw.props.brushColor, brushRadius: canvasDraw.props.brushRadius }); return _this9; }); _defineProperty(this, "handleDrawEnd", function (e, canvasDraw) { e.preventDefault(); // Draw to this end pos _this9.handleDrawMove(e, canvasDraw); canvasDraw.saveLine(); return new DefaultState(); }); this.isDrawing = false; }; exports.DrawingState = DrawingState; var SyntheticEvent = function SyntheticEvent(_ref) { var clientX = _ref.clientX, clientY = _ref.clientY; _defineProperty(this, "preventDefault", function () {}); this.clientX = clientX; this.clientY = clientY; this.touches = [{ clientX: clientX, clientY: clientY }]; }; exports.SyntheticEvent = SyntheticEvent; function clientPointFromEvent(e) { // use cursor pos as default var clientX = e.clientX; var clientY = e.clientY; // use first touch if available if (e.changedTouches && e.changedTouches.length > 0) { clientX = e.changedTouches[0].clientX; clientY = e.changedTouches[0].clientY; } return { clientX: clientX, clientY: clientY }; } function viewPointFromEvent(coordSystem, e) { return coordSystem.clientPointToViewPoint(clientPointFromEvent(e)); }