UNPKG

@nutui/nutui-react

Version:

京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序

480 lines (479 loc) 19.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "AvatarCropper", { enumerable: true, get: function() { return AvatarCropper; } }); var _interop_require_default = require("@swc/helpers/_/_interop_require_default"); var _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard"); var _async_to_generator = require("@swc/helpers/_/_async_to_generator"); var _object_spread = require("@swc/helpers/_/_object_spread"); var _object_spread_props = require("@swc/helpers/_/_object_spread_props"); var _object_without_properties = require("@swc/helpers/_/_object_without_properties"); var _sliced_to_array = require("@swc/helpers/_/_sliced_to_array"); var _ts_generator = require("@swc/helpers/_/_ts_generator"); var _react = /*#__PURE__*/ _interop_require_wildcard._(require("react")); var _classnames = /*#__PURE__*/ _interop_require_default._(require("classnames")); var _button = /*#__PURE__*/ _interop_require_default._(require("../button")); var _typings = require("../../utils/typings"); var _usetouch = require("../../hooks/use-touch"); var _utils = require("../../utils"); var _getrect = require("../../utils/get-rect"); var _configprovider = require("../configprovider"); var defaultProps = (0, _object_spread_props._)((0, _object_spread._)({}, _typings.ComponentDefaults), { maxZoom: 3, space: 10, toolbar: [ /*#__PURE__*/ _react.default.createElement(_button.default, { type: "danger", key: "cancel" }, "Cancel"), /*#__PURE__*/ _react.default.createElement(_button.default, { key: "reset" }, "Reset"), /*#__PURE__*/ _react.default.createElement(_button.default, { key: "rotate" }, "Rotate"), /*#__PURE__*/ _react.default.createElement(_button.default, { type: "success", key: "confirm" }, "Confirm") ], toolbarPosition: 'bottom', editText: 'Edit', shape: 'square' }); var classPrefix = "nut-avatar-cropper"; var AvatarCropper = function AvatarCropper(props) { var locale = (0, _configprovider.useConfig)().locale; defaultProps.toolbar = [ /*#__PURE__*/ _react.default.createElement(_button.default, { type: "danger", key: "cancel" }, locale.cancel), /*#__PURE__*/ _react.default.createElement(_button.default, { key: "reset" }, locale.reset), /*#__PURE__*/ _react.default.createElement(_button.default, { key: "rotate" }, locale.avatarCropper.rotate), /*#__PURE__*/ _react.default.createElement(_button.default, { type: "success", key: "confirm" }, locale.confirm) ]; var _ref = (0, _object_spread._)({}, defaultProps, props), children = _ref.children, maxZoom = _ref.maxZoom, space = _ref.space, toolbar = _ref.toolbar, toolbarPosition = _ref.toolbarPosition, editText = _ref.editText, shape = _ref.shape, className = _ref.className, style = _ref.style, onConfirm = _ref.onConfirm, onCancel = _ref.onCancel, rest = (0, _object_without_properties._)(_ref, [ "children", "maxZoom", "space", "toolbar", "toolbarPosition", "editText", "shape", "className", "style", "onConfirm", "onCancel" ]); var cls = (0, _classnames.default)(classPrefix, className, shape === 'round' && 'round'); var toolbarPositionCls = (0, _classnames.default)("".concat(classPrefix, "-popup-toolbar"), toolbarPosition); var inputImageRef = (0, _react.useRef)(null); var cropperPopupRef = (0, _react.useRef)(null); var canvasRef = (0, _react.useRef)(null); var _useState = (0, _sliced_to_array._)((0, _react.useState)(false), 2), visible = _useState[0], setVisible = _useState[1]; var _useState1 = (0, _sliced_to_array._)((0, _react.useState)(false), 2), moving = _useState1[0], setMoving = _useState1[1]; var _useState2 = (0, _sliced_to_array._)((0, _react.useState)(false), 2), zooming = _useState2[0], setZooming = _useState2[1]; var _useState3 = (0, _sliced_to_array._)((0, _react.useState)({ defScale: 1, scale: 1, angle: 0, moveX: 0, moveY: 0, displayWidth: 0, displayHeight: 0 }), 2), state = _useState3[0], setState = _useState3[1]; var defDrawImage = { img: new Image(), sx: 0, sy: 0, swidth: 0, sheight: 0, x: 0, y: 0, width: 0, height: 0 }; var _useState4 = (0, _sliced_to_array._)((0, _react.useState)((0, _object_spread._)({}, defDrawImage)), 2), drawImage = _useState4[0], setDrawImg = _useState4[1]; var devicePixelRatio = window.devicePixelRatio || 1; var touch = (0, _usetouch.useTouch)(); var highlightStyle = (0, _react.useMemo)(function() { var width = "".concat(drawImage.swidth / devicePixelRatio, "px"); var height = width; return { width: width, height: height, borderRadius: shape === 'round' ? '50%' : '' }; }, [ devicePixelRatio, drawImage.swidth ]); // 是否是横向 var isAngle = (0, _react.useMemo)(function() { return state.angle === 90 || state.angle === 270; }, [ state.angle ]); // 最大横向移动距离 var maxMoveX = (0, _react.useMemo)(function() { var swidth = drawImage.swidth, height = drawImage.height; if (isAngle) { return Math.max(0, (height * state.scale - swidth) / 2); } return Math.max(0, (state.displayWidth * state.scale - swidth) / 2); }, [ state.scale, state.displayWidth, drawImage, isAngle ]); // 最大纵向移动距离 var maxMoveY = (0, _react.useMemo)(function() { var swidth = drawImage.swidth, height = drawImage.height; if (isAngle) { return Math.max(0, (state.displayWidth * state.scale - swidth) / 2); } return Math.max(0, (height * state.scale - swidth) / 2); }, [ state.scale, state.displayWidth, drawImage, isAngle ]); // 文件转base64 var fileToDataURL = function fileToDataURL(file) { return new Promise(function(resolve) { var reader = new FileReader(); reader.onloadend = function(e) { return resolve(e.target.result); }; reader.readAsDataURL(file); }); }; // base64转图片 var dataURLToImage = function dataURLToImage(dataURL) { return new Promise(function(resolve) { var img = new Image(); img.onload = function() { return resolve(img); }; img.src = dataURL; }); }; // 绘制 var draw = (0, _react.useCallback)(function() { var img = drawImage.img, width = drawImage.width, height = drawImage.height, x = drawImage.x, y = drawImage.y, swidth = drawImage.swidth; var moveX = state.moveX, moveY = state.moveY, scale = state.scale, angle = state.angle, displayWidth = state.displayWidth, displayHeight = state.displayHeight; var canvas = canvasRef.current; if (!canvas) return; var ctx = canvas.getContext('2d'); canvas.width = displayWidth; canvas.height = displayHeight; ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#666'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#000'; ctx.fillRect(space * devicePixelRatio, (canvas.height - swidth) / 2, swidth, swidth); ctx.translate(canvas.width / 2 + moveX, canvas.height / 2 + moveY); ctx.rotate(Math.PI / 180 * angle); ctx.scale(scale, scale); ctx.drawImage(img, x, y, width, height); }, [ drawImage, state, devicePixelRatio, space ]); (0, _react.useEffect)(function() { if (Math.abs(state.moveX) > maxMoveX) { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { moveX: maxMoveX })); } if (Math.abs(state.moveY) > maxMoveY) { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { moveY: maxMoveY })); } draw(); }, [ state, maxMoveX, maxMoveY, draw ]); // 设置绘制图片 var setDrawImgs = function setDrawImgs(image) { var rect = (0, _getrect.getRect)(cropperPopupRef.current); if (!rect) return; var clientWidth = rect.width, clientHeight = rect.height; var canvasWidth = state.displayWidth = clientWidth * devicePixelRatio; var canvasHeight = state.displayHeight = clientHeight * devicePixelRatio; var copyDrawImg = (0, _object_spread._)({}, defDrawImage); var imgWidth = image.width, imgHeight = image.height; copyDrawImg.img = image; var isPortrait = imgHeight > imgWidth; var rate = isPortrait ? imgWidth / imgHeight : imgHeight / imgWidth; copyDrawImg.width = canvasWidth; copyDrawImg.height = isPortrait ? canvasWidth / rate : canvasWidth * rate; copyDrawImg.x = -copyDrawImg.width / 2; copyDrawImg.y = -copyDrawImg.height / 2; copyDrawImg.swidth = canvasWidth - space * 2 * devicePixelRatio; copyDrawImg.sheight = isPortrait ? copyDrawImg.swidth / rate : copyDrawImg.swidth * rate; copyDrawImg.sx = space * devicePixelRatio; copyDrawImg.sy = (canvasHeight - copyDrawImg.swidth) / 2; setDrawImg(copyDrawImg); var scale = copyDrawImg.swidth / (isPortrait ? copyDrawImg.width : copyDrawImg.height); setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { defScale: scale })); resetScale(scale); }; var inputImageChange = /*#__PURE__*/ function() { var _ref = (0, _async_to_generator._)(function(event) { var $el, files, base64, image; return (0, _ts_generator._)(this, function(_state) { switch(_state.label){ case 0: setVisible(true); $el = event.target; files = $el.files; if (!(files === null || files === void 0 ? void 0 : files.length)) return [ 2 ]; return [ 4, fileToDataURL(files[0]) ]; case 1: base64 = _state.sent(); return [ 4, dataURLToImage(base64) ]; case 2: image = _state.sent(); setDrawImgs(image); return [ 2 ]; } }); }); return function inputImageChange(event) { return _ref.apply(this, arguments); }; }(); var resetScale = function resetScale(scale) { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { moveX: 0, moveY: 0, angle: 0, scale: scale || state.defScale, defScale: scale || state.defScale })); }; var setScale = function setScale(scale) { scale = (0, _utils.clamp)(scale, +0.3, +maxZoom + 1); if (scale !== state.scale) { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { scale: scale })); } }; // 计算两个点的距离 var getDistance = function getDistance(touches) { return Math.sqrt(Math.pow(touches[0].clientX - touches[1].clientX, 2) + Math.pow(touches[0].clientY - touches[1].clientY, 2)); }; var _useState5 = (0, _sliced_to_array._)((0, _react.useState)({ startMoveX: 0, startMoveY: 0, startScale: 0, startDistance: 0 }), 2), startMove = _useState5[0], setStartMove = _useState5[1]; var startMoveX = startMove.startMoveX, startMoveY = startMove.startMoveY, startScale = startMove.startScale, startDistance = startMove.startDistance; var onTouchStart = function onTouchStart(event) { var touches = event.touches; var offsetX = touch.offsetX; touch.start(event); var fingerNum = touches === null || touches === void 0 ? void 0 : touches.length; setStartMove((0, _object_spread_props._)((0, _object_spread._)({}, startMove), { startMoveX: state.moveX, startMoveY: state.moveY })); setMoving(fingerNum === 1); setZooming(fingerNum === 2 && !offsetX.current); if (fingerNum === 2 && !offsetX.current) { setStartMove((0, _object_spread_props._)((0, _object_spread._)({}, startMove), { startScale: state.scale, startDistance: getDistance(event.touches) })); } }; var onTouchMove = function onTouchMove(event) { var touches = event.touches; touch.move(event); if (moving || zooming) { (0, _utils.preventDefault)(event, true); } if (moving) { var deltaX = touch.deltaX, deltaY = touch.deltaY; var moveX = deltaX.current * state.scale + startMoveX; var moveY = deltaY.current * state.scale + startMoveY; setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { moveX: (0, _utils.clamp)(moveX, -maxMoveX, maxMoveX), moveY: (0, _utils.clamp)(moveY, -maxMoveY, maxMoveY) })); } if (zooming && touches.length === 2) { var distance = getDistance(touches); var scale = startScale * distance / startDistance; setScale(scale); } }; var onTouchEnd = function onTouchEnd(event) { var stopPropagation = false; if (moving || zooming) { stopPropagation = !(moving && startMoveX === state.moveX && startMoveY === state.moveY); if (!event.touches.length) { if (zooming) { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { moveX: (0, _utils.clamp)(state.moveX, -maxMoveX, maxMoveX), moveY: (0, _utils.clamp)(state.moveY, -maxMoveY, maxMoveY) })); setZooming(false); } setMoving(false); setStartMove((0, _object_spread_props._)((0, _object_spread._)({}, startMove), { startMoveX: 0, startMoveY: 0, startScale: state.defScale })); if (state.scale < state.defScale) { resetScale(); } if (state.scale > maxZoom) { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { scale: +maxZoom })); } } } (0, _utils.preventDefault)(event, stopPropagation); touch.reset(); }; var reset = function reset() { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { angle: 0 })); }; var rotate = function rotate() { if (state.angle === 270) { setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { angle: 0 })); return; } setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { angle: state.angle + 90 })); }; var cancel = function cancel() { var isEmit = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : true; setVisible(false); resetScale(); inputImageRef.current && (inputImageRef.current.value = ''); isEmit && onCancel && onCancel(); }; // 裁剪图片 var confirm = function confirm() { var canvas = canvasRef.current; var sx = drawImage.sx, sy = drawImage.sy, swidth = drawImage.swidth; var width = swidth; var height = swidth; // 创建一个新的canvas元素,用于裁剪后的内容 var croppedCanvas = document.createElement('canvas'); var croppedCtx = croppedCanvas.getContext('2d'); // 设置新canvas的大小与裁剪区域相同 croppedCanvas.width = width; croppedCanvas.height = height; // 使用drawImage方法将原canvas中指定区域的内容绘制到新canvas上 canvas && croppedCtx.drawImage(canvas, sx, sy, width, height, 0, 0, width, height); // 将裁剪后的内容转换为图片格式 var imageDataURL = croppedCanvas.toDataURL('image/png'); onConfirm && onConfirm(imageDataURL); cancel(false); }; var ToolBar = function ToolBar() { var actions = [ cancel, reset, rotate, confirm ]; return /*#__PURE__*/ _react.default.createElement("div", { className: "".concat(classPrefix, "-popup-toolbar-flex") }, actions.map(function(action, index) { return /*#__PURE__*/ _react.default.createElement("div", { key: index, className: "".concat(classPrefix, "-popup-toolbar-item"), onClick: function onClick(_e) { return action(); } }, toolbar[index]); })); }; var CropperPopup = function CropperPopup() { return /*#__PURE__*/ _react.default.createElement("div", { ref: cropperPopupRef, className: "".concat(classPrefix, "-popup"), style: { display: visible ? 'block' : 'none' } }, /*#__PURE__*/ _react.default.createElement("canvas", { ref: canvasRef, className: "".concat(classPrefix, "-popup-canvas") }), /*#__PURE__*/ _react.default.createElement("div", { className: "".concat(classPrefix, "-popup-highlight"), onTouchStart: onTouchStart, onTouchMove: onTouchMove, onTouchEnd: onTouchEnd }, /*#__PURE__*/ _react.default.createElement("div", { className: "highlight", style: highlightStyle })), /*#__PURE__*/ _react.default.createElement("div", { className: toolbarPositionCls }, /*#__PURE__*/ _react.default.createElement(ToolBar, null))); }; return /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/ _react.default.createElement("div", (0, _object_spread_props._)((0, _object_spread._)({ className: cls }, rest), { style: style }), children, /*#__PURE__*/ _react.default.createElement("input", { ref: inputImageRef, type: "file", accept: "image/*", className: "".concat(classPrefix, "-input"), onChange: function onChange(e) { return inputImageChange(e); }, "aria-label": locale.avatarCropper.selectImage }), /*#__PURE__*/ _react.default.createElement("div", { className: "nut-avatar-cropper-edit-text" }, editText)), CropperPopup()); }; AvatarCropper.displayName = 'NutAvatarCropper';