UNPKG

@nutui/nutui-react-taro

Version:

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

663 lines (662 loc) 27.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 _taro = /*#__PURE__*/ _interop_require_wildcard._(require("@tarojs/taro")); var _classnames = /*#__PURE__*/ _interop_require_default._(require("classnames")); var _components = require("@tarojs/components"); var _getsysteminfo = require("../../utils/taro/get-system-info"); var _button = require("../button/button"); var _index = require("../configprovider/index"); var _typings = require("../../utils/typings"); var _usetouch = require("../../hooks/use-touch"); var _utils = require("../../utils"); var defaultProps = (0, _object_spread_props._)((0, _object_spread._)({}, _typings.ComponentDefaults), { maxZoom: 3, space: 10, toolbar: [ /*#__PURE__*/ _react.default.createElement(_button.Button, { type: "danger", key: "cancel" }, "Cancel"), /*#__PURE__*/ _react.default.createElement(_button.Button, { key: "reset" }, "Reset"), /*#__PURE__*/ _react.default.createElement(_button.Button, { key: "rotate" }, "Rotate"), /*#__PURE__*/ _react.default.createElement(_button.Button, { type: "success", key: "confirm" }, "Confirm") ], toolbarPosition: 'bottom', editText: 'Edit', sizeType: [ 'original', 'compressed' ], sourceType: [ 'album', 'camera' ], shape: 'square' }); var classPrefix = "nut-avatar-cropper"; var AvatarCropper = function AvatarCropper(props) { var locale = (0, _index.useConfig)().locale; defaultProps.toolbar = [ /*#__PURE__*/ _react.default.createElement(_button.Button, { type: "danger", key: "cancel" }, locale.cancel), /*#__PURE__*/ _react.default.createElement(_button.Button, { key: "reset" }, locale.reset), /*#__PURE__*/ _react.default.createElement(_button.Button, { key: "rotate" }, locale.avatarCropper.rotate), /*#__PURE__*/ _react.default.createElement(_button.Button, { type: "success", key: "confirm" }, locale.confirm) ]; defaultProps.editText = locale.edit; var _ref = (0, _object_spread._)({}, defaultProps, props), children = _ref.children, toolbar = _ref.toolbar, maxZoom = _ref.maxZoom, space = _ref.space, toolbarPosition = _ref.toolbarPosition, editText = _ref.editText, sizeType = _ref.sizeType, sourceType = _ref.sourceType, shape = _ref.shape, className = _ref.className, style = _ref.style, onConfirm = _ref.onConfirm, onCancel = _ref.onCancel, rest = (0, _object_without_properties._)(_ref, [ "children", "toolbar", "maxZoom", "space", "toolbarPosition", "editText", "sizeType", "sourceType", "shape", "className", "style", "onConfirm", "onCancel" ]); var cls = (0, _classnames.default)(classPrefix, 'taro', className, shape === 'round' && 'round'); var toolbarPositionCls = (0, _classnames.default)("".concat(classPrefix, "-popup-toolbar"), toolbarPosition); 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 systemInfo = (0, _getsysteminfo.getWindowInfo)(); // 支付宝基础库2.7.0以上支持,需要开启支付宝小程序canvas2d var showAlipayCanvas2D = (0, _react.useMemo)(function() { return _taro.default.getEnv() === 'ALIPAY' && parseInt(_taro.default.SDKVersion.replace(/\./g, '')) >= 270; }, []); var showPixelRatio = _taro.default.getEnv() === 'WEB' || showAlipayCanvas2D; // 设备像素比 var pixelRatio = showPixelRatio ? systemInfo.pixelRatio : 1; var _useState3 = (0, _sliced_to_array._)((0, _react.useState)({ defScale: 1, scale: 1, angle: 0, moveX: 0, moveY: 0, displayWidth: systemInfo.windowWidth * pixelRatio, displayHeight: systemInfo.windowHeight * pixelRatio, cropperWidth: systemInfo.windowWidth * pixelRatio - space * pixelRatio * 2, cropperHeight: systemInfo.windowWidth * pixelRatio - space * pixelRatio * 2 }), 2), state = _useState3[0], setState = _useState3[1]; var defDrawImage = { src: '', 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 _useState5 = (0, _sliced_to_array._)((0, _react.useState)({ canvasId: "canvas-".concat(Date.now()), cropperCanvas: null, cropperCanvasContext: null }), 2), canvasAll = _useState5[0], setCanvasAll = _useState5[1]; (0, _taro.useReady)(function() { if (showAlipayCanvas2D) { var canvasId = canvasAll.canvasId; (0, _taro.createSelectorQuery)().select("#".concat(canvasId)).node(function(param) { var canvas = param.node; canvas.width = state.displayWidth; canvas.height = state.displayHeight; setCanvasAll((0, _object_spread_props._)((0, _object_spread._)({}, canvasAll), { cropperCanvas: canvas })); }).exec(); } }); (0, _react.useEffect)(function() { setCanvasAll((0, _object_spread_props._)((0, _object_spread._)({}, canvasAll), { cropperCanvasContext: _taro.default.createCanvasContext(canvasAll.canvasId) })); }, []); var touch = (0, _usetouch.useTouch)(); var highlightStyle = (0, _react.useMemo)(function() { var width = "".concat(state.cropperWidth / pixelRatio, "px"); var height = width; return { width: width, height: height, borderRadius: shape === 'round' ? '50%' : '' }; }, [ pixelRatio, state.cropperWidth ]); // 是否是横向 var isAngle = (0, _react.useMemo)(function() { return state.angle === 90 || state.angle === 270; }, [ state.angle ]); var canvasStyle = (0, _react.useMemo)(function() { return { width: "".concat(state.displayWidth / pixelRatio, "px"), height: "".concat(state.displayHeight / pixelRatio, "px") }; }, [ pixelRatio, state.displayHeight, state.displayWidth ]); // 最大横向移动距离 var maxMoveX = (0, _react.useMemo)(function() { var displayWidth = state.displayWidth, scale = state.scale, cropperWidth = state.cropperWidth; if (isAngle) { return Math.max(0, (drawImage.height * scale - cropperWidth) / 2); } return Math.max(0, (displayWidth * scale - cropperWidth) / 2); }, [ drawImage.height, isAngle, state ]); // 最大纵向移动距离 var maxMoveY = (0, _react.useMemo)(function() { var displayWidth = state.displayWidth, scale = state.scale, cropperWidth = state.cropperWidth; if (isAngle) { return Math.max(0, (displayWidth * scale - cropperWidth) / 2); } return Math.max(0, (drawImage.height * scale - cropperWidth) / 2); }, [ drawImage.height, isAngle, state ]); // base64转图片 var dataURLToImage = function dataURLToImage(dataURL) { return new Promise(function(resolve) { var img = new Image(); img.onload = function() { return resolve(img); }; img.src = dataURL; }); }; // base64转图片(canvasImage) var dataURLToCanvasImage = function dataURLToCanvasImage(canvas, dataURL) { return new Promise(function(resolve) { var img = canvas.createImage(); img.onload = function() { return resolve(img); }; img.src = dataURL; }); }; var canvas2dDraw = (0, _react.useCallback)(function(ctx) { var src = drawImage.src, width = drawImage.width, height = drawImage.height, x = drawImage.x, y = drawImage.y; if (!ctx || !src) return; var moveX = state.moveX, moveY = state.moveY, scale = state.scale, angle = state.angle, displayWidth = state.displayWidth, displayHeight = state.displayHeight, cropperWidth = state.cropperWidth; ctx.clearRect(0, 0, displayWidth, displayHeight); ctx.fillStyle = '#666'; ctx.fillRect(0, 0, displayWidth, displayHeight); ctx.fillStyle = '#000'; ctx.fillRect(space * pixelRatio, (displayHeight - cropperWidth) / 2, cropperWidth, cropperWidth); ctx.translate(displayWidth / 2 + moveX, displayHeight / 2 + moveY); ctx.rotate(Math.PI / 180 * angle); ctx.scale(scale, scale); ctx.drawImage(src, x, y, width, height); }, [ drawImage, state ]); // web绘制 var webDraw = (0, _react.useCallback)(function() { var canvasDom = document.getElementById(canvasAll.canvasId); var canvas = canvasDom; if ((canvasDom === null || canvasDom === void 0 ? void 0 : canvasDom.tagName) !== 'CANVAS') { canvas = canvasDom === null || canvasDom === void 0 ? void 0 : canvasDom.getElementsByTagName('canvas')[0]; } if (!canvas) return; canvas.width = state.displayWidth; canvas.height = state.displayHeight; var ctx = canvas.getContext('2d'); canvas2dDraw(ctx); }, [ canvas2dDraw ]); var alipayDraw = (0, _react.useCallback)(function() { var ctx = canvasAll.cropperCanvas.getContext('2d'); ctx && ctx.resetTransform(); canvas2dDraw(ctx); }, [ canvas2dDraw, canvasAll.cropperCanvas ]); // 绘制显示的canvas内容 var draw = (0, _react.useCallback)(function() { if (_taro.default.getEnv() === 'WEB') { webDraw(); return; } if (showAlipayCanvas2D) { alipayDraw(); return; } var src = drawImage.src, width = drawImage.width, height = drawImage.height, x = drawImage.x, y = drawImage.y; var moveX = state.moveX, moveY = state.moveY, scale = state.scale, angle = state.angle, displayWidth = state.displayWidth, displayHeight = state.displayHeight, cropperWidth = state.cropperWidth; var cropperCanvasContext = canvasAll.cropperCanvasContext; var ctx = cropperCanvasContext; if (!ctx) return; // 绘制背景 ctx.clearRect(0, 0, displayWidth, displayHeight); ctx.fillStyle = '#666'; ctx.setFillStyle('#666'); ctx.fillRect(0, 0, displayWidth, displayHeight); ctx.stroke(); ctx.fill(); ctx.fillStyle = '#000'; ctx.setFillStyle('#000'); ctx.fillRect(space, (displayHeight - cropperWidth) / 2, cropperWidth, cropperWidth); ctx.stroke(); ctx.fill(); // 绘制偏移量 ctx.translate(displayWidth / 2 + moveX, displayHeight / 2 + moveY); ctx.rotate(Math.PI / 180 * angle); ctx.scale(scale, scale); ctx.drawImage(src, x, y, width, height); ctx.draw(); }, [ drawImage, state.scale, state.angle, state.moveX, state.moveY ]); (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 setDrawImgInfo = function setDrawImgInfo(image) { return (0, _async_to_generator._)(function() { var displayWidth, cropperWidth, copyDrawImg, imgWidth, imgHeight, isPortrait, rate, scale; return (0, _ts_generator._)(this, function(_state) { switch(_state.label){ case 0: displayWidth = state.displayWidth, cropperWidth = state.cropperWidth; copyDrawImg = (0, _object_spread._)({}, defDrawImage); imgWidth = image.width, imgHeight = image.height; copyDrawImg.src = image.path; if (!(_taro.default.getEnv() === 'WEB')) return [ 3, 2 ]; return [ 4, dataURLToImage(image.path) ]; case 1: copyDrawImg.src = _state.sent(); _state.label = 2; case 2: if (!showAlipayCanvas2D) return [ 3, 4 ]; return [ 4, dataURLToCanvasImage(canvasAll.cropperCanvas, image.path) ]; case 3: copyDrawImg.src = _state.sent(); _state.label = 4; case 4: isPortrait = imgHeight > imgWidth; rate = isPortrait ? imgWidth / imgHeight : imgHeight / imgWidth; copyDrawImg.width = displayWidth; copyDrawImg.height = isPortrait ? displayWidth / rate : displayWidth * rate; copyDrawImg.x = -copyDrawImg.width / 2; copyDrawImg.y = -copyDrawImg.height / 2; setDrawImg(copyDrawImg); scale = cropperWidth / (isPortrait ? copyDrawImg.width : copyDrawImg.height); setState((0, _object_spread_props._)((0, _object_spread._)({}, state), { defScale: scale })); resetScale(scale); return [ 2 ]; } }); })(); }; var chooseImage = function chooseImage() { _taro.default.chooseImage({ count: 1, // 可以指定是原图还是压缩图,默认二者都有 sizeType: sizeType, sourceType: sourceType, success: function success(res) { // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片 var tempFiles = res.tempFiles; !!tempFiles.length && imageChange(tempFiles[0]); } }); }; var imageChange = function imageChange(file) { return (0, _async_to_generator._)(function() { return (0, _ts_generator._)(this, function(_state) { _taro.default.getImageInfo({ src: file.path }).then(function(res) { setVisible(true); setDrawImgInfo(res); }); return [ 2 ]; }); })(); }; 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 _useState6 = (0, _sliced_to_array._)((0, _react.useState)({ startMoveX: 0, startMoveY: 0, startScale: 0, startDistance: 0 }), 2), startMove = _useState6[0], setStartMove = _useState6[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(); isEmit && onCancel && onCancel(); }; // web裁剪图片 var confirmWEB = function confirmWEB() { var cropperWidth = state.cropperWidth, displayHeight = state.displayHeight; var canvasDom = document.getElementById(canvasAll.canvasId); var canvas = canvasDom; if ((canvasDom === null || canvasDom === void 0 ? void 0 : canvasDom.tagName) !== 'CANVAS') { canvas = canvasDom === null || canvasDom === void 0 ? void 0 : canvasDom.getElementsByTagName('canvas')[0]; } var width = cropperWidth; var height = cropperWidth; // 创建一个新的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, space * pixelRatio, (displayHeight - cropperWidth) / 2, width, height, 0, 0, width, height); // 将裁剪后的内容转换为图片格式 var imageDataURL = croppedCanvas.toDataURL('image/png'); onConfirm && onConfirm(imageDataURL); cancel(false); }; // 支付宝基础库2.7.0以上支持,需要开启支付宝小程序canvas2d var confirmALIPAY = function confirmALIPAY() { var cropperWidth = state.cropperWidth, displayHeight = state.displayHeight; var cropperCanvas = canvasAll.cropperCanvas; _taro.default.canvasToTempFilePath({ canvas: cropperCanvas, x: props.space, y: (displayHeight - cropperWidth) / 2, width: cropperWidth, height: cropperWidth, destWidth: cropperWidth, destHeight: cropperWidth, success: function success(res) { return (0, _async_to_generator._)(function() { var filePath; return (0, _ts_generator._)(this, function(_state) { filePath = res.tempFilePath; onConfirm && onConfirm(filePath); cancel(false); return [ 2 ]; }); })(); } }); }; // 裁剪图片 var confirm = function confirm() { if (_taro.default.getEnv() === 'WEB') { confirmWEB(); return; } if (showAlipayCanvas2D) { confirmALIPAY(); return; } var cropperWidth = state.cropperWidth, displayHeight = state.displayHeight; var canvasId = canvasAll.canvasId; // 将编辑后的canvas内容转成图片 _taro.default.canvasToTempFilePath({ canvasId: canvasId, x: props.space, y: (displayHeight - cropperWidth) / 2, width: cropperWidth, height: cropperWidth, destWidth: cropperWidth * systemInfo.pixelRatio, destHeight: cropperWidth * systemInfo.pixelRatio, success: function success(res) { return (0, _async_to_generator._)(function() { var filePath; return (0, _ts_generator._)(this, function(_state) { filePath = res.tempFilePath; onConfirm && onConfirm(filePath); cancel(false); return [ 2 ]; }); })(); } }); }; var ToolBar = function ToolBar() { var actions = [ cancel, reset, rotate, confirm ]; return /*#__PURE__*/ _react.default.createElement(_components.View, { className: "".concat(classPrefix, "-popup-toolbar-flex") }, actions.map(function(action, index) { return /*#__PURE__*/ _react.default.createElement(_components.View, { key: index, className: "".concat(classPrefix, "-popup-toolbar-item"), onClick: function onClick(_e) { return action(); } }, toolbar[index]); })); }; var CropperPopup = function CropperPopup() { var canvasId = canvasAll.canvasId; return /*#__PURE__*/ _react.default.createElement(_components.View, { className: "".concat(classPrefix, "-popup"), style: { display: visible ? 'block' : 'none' } }, /*#__PURE__*/ _react.default.createElement(_components.Canvas, { id: canvasId, "canvas-id": canvasId, type: showAlipayCanvas2D ? '2d' : undefined, style: canvasStyle, className: "".concat(classPrefix, "-popup-canvas") }), /*#__PURE__*/ _react.default.createElement(_components.View, { className: "".concat(classPrefix, "-popup-highlight"), onTouchStart: onTouchStart, onTouchMove: onTouchMove, onTouchEnd: onTouchEnd }, /*#__PURE__*/ _react.default.createElement(_components.View, { className: "highlight", style: highlightStyle })), /*#__PURE__*/ _react.default.createElement(_components.View, { className: toolbarPositionCls }, /*#__PURE__*/ _react.default.createElement(ToolBar, null))); }; return /*#__PURE__*/ _react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/ _react.default.createElement(_components.View, (0, _object_spread_props._)((0, _object_spread._)({ className: cls }, rest), { style: style }), children, /*#__PURE__*/ _react.default.createElement(_components.View, { className: "nut-avatar-cropper-edit-text", onClick: chooseImage }, editText)), CropperPopup()); }; AvatarCropper.displayName = 'NutAvatarCropper';