UNPKG

react-canvas-knob

Version:

HTML5 Canvas-based React knob/dial component

403 lines (351 loc) 15.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _redboxReact2 = require('redbox-react'); var _redboxReact3 = _interopRequireDefault(_redboxReact2); var _reactTransformCatchErrors3 = require('react-transform-catch-errors'); var _reactTransformCatchErrors4 = _interopRequireDefault(_reactTransformCatchErrors3); var _react2 = require('react'); var _react3 = _interopRequireDefault(_react2); var _reactTransformHmr3 = require('react-transform-hmr'); var _reactTransformHmr4 = _interopRequireDefault(_reactTransformHmr3); var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _class, _temp; var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var _components = { Knob: { displayName: 'Knob' } }; var _reactTransformHmr2 = (0, _reactTransformHmr4.default)({ filename: 'Knob.js', components: _components, locals: [module], imports: [_react3.default] }); var _reactTransformCatchErrors2 = (0, _reactTransformCatchErrors4.default)({ filename: 'Knob.js', components: _components, locals: [], imports: [_react3.default, _redboxReact3.default] }); function _wrapComponent(id) { return function (Component) { return _reactTransformHmr2(_reactTransformCatchErrors2(Component, id), id); }; } var Knob = _wrapComponent('Knob')((_temp = _class = function (_React$Component) { _inherits(Knob, _React$Component); function Knob(props) { _classCallCheck(this, Knob); var _this = _possibleConstructorReturn(this, (Knob.__proto__ || Object.getPrototypeOf(Knob)).call(this, props)); _this.getArcToValue = function (v) { var startAngle = void 0; var endAngle = void 0; var angle = !_this.props.log ? (v - _this.props.min) * _this.angleArc / (_this.props.max - _this.props.min) : Math.log(Math.pow(v / _this.props.min, _this.angleArc)) / Math.log(_this.props.max / _this.props.min); if (!_this.props.clockwise) { startAngle = _this.endAngle + 0.00001; endAngle = startAngle - angle - 0.00001; } else { startAngle = _this.startAngle - 0.00001; endAngle = startAngle + angle + 0.00001; } if (_this.props.cursor) { startAngle = endAngle - _this.cursorExt; endAngle += _this.cursorExt; } return { startAngle: startAngle, endAngle: endAngle, acw: !_this.props.clockwise && !_this.props.cursor }; }; _this.getCanvasScale = function (ctx) { var devicePixelRatio = window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI || // IE10 1; var backingStoreRatio = ctx.webkitBackingStorePixelRatio || 1; return devicePixelRatio / backingStoreRatio; }; _this.coerceToStep = function (v) { var val = !_this.props.log ? ~~((v < 0 ? -0.5 : 0.5) + v / _this.props.step) * _this.props.step : Math.pow(_this.props.step, ~~((Math.abs(v) < 1 ? -0.5 : 0.5) + Math.log(v) / Math.log(_this.props.step))); val = Math.max(Math.min(val, _this.props.max), _this.props.min); if (isNaN(val)) { val = 0; } return Math.round(val * 1000) / 1000; }; _this.eventToValue = function (e) { var bounds = _this.canvasRef.getBoundingClientRect(); var x = e.clientX - bounds.left; var y = e.clientY - bounds.top; var a = Math.atan2(x - _this.w / 2, _this.w / 2 - y) - _this.angleOffset; if (!_this.props.clockwise) { a = _this.angleArc - a - 2 * Math.PI; } if (_this.angleArc !== Math.PI * 2 && a < 0 && a > -0.5) { a = 0; } else if (a < 0) { a += Math.PI * 2; } var val = !_this.props.log ? a * (_this.props.max - _this.props.min) / _this.angleArc + _this.props.min : Math.pow(_this.props.max / _this.props.min, a / _this.angleArc) * _this.props.min; return _this.coerceToStep(val); }; _this.handleMouseDown = function (e) { _this.props.onChange(_this.eventToValue(e)); document.addEventListener('mousemove', _this.handleMouseMove); document.addEventListener('mouseup', _this.handleMouseUp); document.addEventListener('keyup', _this.handleEsc); }; _this.handleMouseMove = function (e) { e.preventDefault(); _this.props.onChange(_this.eventToValue(e)); }; _this.handleMouseUp = function (e) { _this.props.onChangeEnd(_this.eventToValue(e)); document.removeEventListener('mousemove', _this.handleMouseMove); document.removeEventListener('mouseup', _this.handleMouseUp); document.removeEventListener('keyup', _this.handleEsc); }; _this.handleTouchStart = function (e) { e.preventDefault(); _this.touchIndex = e.targetTouches.length - 1; _this.props.onChange(_this.eventToValue(e.targetTouches[_this.touchIndex])); document.addEventListener('touchmove', _this.handleTouchMove, { passive: false }); document.addEventListener('touchend', _this.handleTouchEnd); document.addEventListener('touchcancel', _this.handleTouchEnd); }; _this.handleTouchMove = function (e) { e.preventDefault(); _this.props.onChange(_this.eventToValue(e.targetTouches[_this.touchIndex])); }; _this.handleTouchEnd = function (e) { _this.props.onChangeEnd(_this.eventToValue(e)); document.removeEventListener('touchmove', _this.handleTouchMove); document.removeEventListener('touchend', _this.handleTouchEnd); document.removeEventListener('touchcancel', _this.handleTouchEnd); }; _this.handleEsc = function (e) { if (e.keyCode === 27) { e.preventDefault(); _this.handleMouseUp(); } }; _this.handleTextInput = function (e) { var val = Math.max(Math.min(+e.target.value, _this.props.max), _this.props.min) || _this.props.min; _this.props.onChange(val); }; _this.handleWheel = function (e) { e.preventDefault(); if (e.deltaX > 0 || e.deltaY > 0) { _this.props.onChange(_this.coerceToStep(!_this.props.log ? _this.props.value + _this.props.step : _this.props.value * _this.props.step)); } else if (e.deltaX < 0 || e.deltaY < 0) { _this.props.onChange(_this.coerceToStep(!_this.props.log ? _this.props.value - _this.props.step : _this.props.value / _this.props.step)); } }; _this.handleArrowKey = function (e) { if (e.keyCode === 37 || e.keyCode === 40) { e.preventDefault(); _this.props.onChange(_this.coerceToStep(!_this.props.log ? _this.props.value - _this.props.step : _this.props.value / _this.props.step)); } else if (e.keyCode === 38 || e.keyCode === 39) { e.preventDefault(); _this.props.onChange(_this.coerceToStep(!_this.props.log ? _this.props.value + _this.props.step : _this.props.value * _this.props.step)); } }; _this.inputStyle = function () { return { width: (_this.w / 2 + 4 >> 0) + 'px', height: (_this.w / 3 >> 0) + 'px', position: 'absolute', verticalAlign: 'middle', marginTop: (_this.w / 3 >> 0) + 'px', marginLeft: '-' + (_this.w * 3 / 4 + 2 >> 0) + 'px', border: 0, background: 'none', font: _this.props.fontWeight + ' ' + (_this.w / _this.digits >> 0) + 'px ' + _this.props.font, textAlign: 'center', color: _this.props.inputColor || _this.props.fgColor, padding: '0px', WebkitAppearance: 'none' }; }; _this.renderCenter = function () { var _this$props = _this.props, displayCustom = _this$props.displayCustom, displayInput = _this$props.displayInput, disableTextInput = _this$props.disableTextInput, readOnly = _this$props.readOnly, value = _this$props.value; if (displayInput) { return _react3.default.createElement('input', { style: _this.inputStyle(), type: 'text', value: value, onChange: _this.handleTextInput, onKeyDown: _this.handleArrowKey, readOnly: readOnly || disableTextInput }); } else if (displayCustom && typeof displayCustom === 'function') { return displayCustom(); } return null; }; _this.w = _this.props.width || 200; _this.h = _this.props.height || _this.w; _this.cursorExt = _this.props.cursor === true ? 0.3 : _this.props.cursor / 100; _this.angleArc = _this.props.angleArc * Math.PI / 180; _this.angleOffset = _this.props.angleOffset * Math.PI / 180; _this.startAngle = 1.5 * Math.PI + _this.angleOffset; _this.endAngle = 1.5 * Math.PI + _this.angleOffset + _this.angleArc; _this.digits = Math.max(String(Math.abs(_this.props.min)).length, String(Math.abs(_this.props.max)).length, 2) + 2; return _this; } _createClass(Knob, [{ key: 'componentDidMount', value: function componentDidMount() { this.drawCanvas(); if (!this.props.readOnly) { this.canvasRef.addEventListener('touchstart', this.handleTouchStart, { passive: false }); } } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { if (nextProps.width && this.w !== nextProps.width) { this.w = nextProps.width; } if (nextProps.height && this.h !== nextProps.height) { this.h = nextProps.height; } } }, { key: 'componentDidUpdate', value: function componentDidUpdate() { this.drawCanvas(); } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this.canvasRef.removeEventListener('touchstart', this.handleTouchStart); } // Calculate ratio to scale canvas to avoid blurriness on HiDPI devices }, { key: 'drawCanvas', value: function drawCanvas() { var ctx = this.canvasRef.getContext('2d'); var scale = this.getCanvasScale(ctx); this.canvasRef.width = this.w * scale; // clears the canvas this.canvasRef.height = this.h * scale; ctx.scale(scale, scale); this.xy = this.w / 2; // coordinates of canvas center this.lineWidth = this.xy * this.props.thickness; this.radius = this.xy - this.lineWidth / 2; ctx.lineWidth = this.lineWidth; ctx.lineCap = this.props.lineCap; // background arc ctx.beginPath(); ctx.strokeStyle = this.props.bgColor; ctx.arc(this.xy, this.xy, this.radius, this.endAngle - 0.00001, this.startAngle + 0.00001, true); ctx.stroke(); // foreground arc var a = this.getArcToValue(this.props.value); ctx.beginPath(); ctx.strokeStyle = this.props.fgColor; ctx.arc(this.xy, this.xy, this.radius, a.startAngle, a.endAngle, a.acw); ctx.stroke(); } }, { key: 'render', value: function render() { var _this2 = this; var _props = this.props, canvasClassName = _props.canvasClassName, className = _props.className, disableMouseWheel = _props.disableMouseWheel, readOnly = _props.readOnly, title = _props.title, value = _props.value; return _react3.default.createElement( 'div', { className: className, style: { width: this.w, height: this.h, display: 'inline-block' }, onWheel: readOnly || disableMouseWheel ? null : this.handleWheel }, _react3.default.createElement('canvas', { ref: function ref(_ref) { _this2.canvasRef = _ref; }, className: canvasClassName, style: { width: '100%', height: '100%' }, onMouseDown: readOnly ? null : this.handleMouseDown, title: title ? title + ': ' + value : value }), this.renderCenter() ); } }]); return Knob; }(_react3.default.Component), _class.propTypes = { value: _propTypes2.default.number.isRequired, onChange: _propTypes2.default.func.isRequired, onChangeEnd: _propTypes2.default.func, min: _propTypes2.default.number, max: _propTypes2.default.number, step: _propTypes2.default.number, log: _propTypes2.default.bool, width: _propTypes2.default.number, height: _propTypes2.default.number, thickness: _propTypes2.default.number, lineCap: _propTypes2.default.oneOf(['butt', 'round']), bgColor: _propTypes2.default.string, fgColor: _propTypes2.default.string, inputColor: _propTypes2.default.string, font: _propTypes2.default.string, fontWeight: _propTypes2.default.string, clockwise: _propTypes2.default.bool, cursor: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.bool]), stopper: _propTypes2.default.bool, readOnly: _propTypes2.default.bool, disableTextInput: _propTypes2.default.bool, displayInput: _propTypes2.default.bool, displayCustom: _propTypes2.default.func, angleArc: _propTypes2.default.number, angleOffset: _propTypes2.default.number, disableMouseWheel: _propTypes2.default.bool, title: _propTypes2.default.string, className: _propTypes2.default.string, canvasClassName: _propTypes2.default.string }, _class.defaultProps = { onChangeEnd: function onChangeEnd() {}, min: 0, max: 100, step: 1, log: false, width: 200, height: 200, thickness: 0.35, lineCap: 'butt', bgColor: '#EEE', fgColor: '#EA2', inputColor: '', font: 'Arial', fontWeight: 'bold', clockwise: true, cursor: false, stopper: true, readOnly: false, disableTextInput: false, displayInput: true, angleArc: 360, angleOffset: 0, disableMouseWheel: false, className: null, canvasClassName: null }, _temp)); exports.default = Knob;