UNPKG

@nutui/nutui-react-taro

Version:

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

651 lines (650 loc) 25.9 kB
import { _ as _async_to_generator } from "@swc/helpers/_/_async_to_generator"; import { _ as _object_spread } from "@swc/helpers/_/_object_spread"; import { _ as _object_spread_props } from "@swc/helpers/_/_object_spread_props"; import { _ as _object_without_properties } from "@swc/helpers/_/_object_without_properties"; import { _ as _sliced_to_array } from "@swc/helpers/_/_sliced_to_array"; import { _ as _ts_generator } from "@swc/helpers/_/_ts_generator"; import React, { useState, useEffect, useMemo, useCallback } from "react"; import Taro, { useReady, createSelectorQuery } from "@tarojs/taro"; import classNames from "classnames"; import { Canvas, View } from "@tarojs/components"; import { getWindowInfo } from "../../utils/taro/get-system-info"; import { Button } from "../button/button"; import { useConfig } from "../configprovider/index"; import { ComponentDefaults } from "../../utils/typings"; import { useTouch } from "../../hooks/use-touch"; import { clamp, preventDefault } from "../../utils"; var defaultProps = _object_spread_props(_object_spread({}, ComponentDefaults), { maxZoom: 3, space: 10, toolbar: [ /*#__PURE__*/ React.createElement(Button, { type: "danger", key: "cancel" }, "Cancel"), /*#__PURE__*/ React.createElement(Button, { key: "reset" }, "Reset"), /*#__PURE__*/ React.createElement(Button, { key: "rotate" }, "Rotate"), /*#__PURE__*/ React.createElement(Button, { type: "success", key: "confirm" }, "Confirm") ], toolbarPosition: 'bottom', editText: 'Edit', sizeType: [ 'original', 'compressed' ], sourceType: [ 'album', 'camera' ], shape: 'square' }); var classPrefix = "nut-avatar-cropper"; export var AvatarCropper = function(props) { var locale = useConfig().locale; defaultProps.toolbar = [ /*#__PURE__*/ React.createElement(Button, { type: "danger", key: "cancel" }, locale.cancel), /*#__PURE__*/ React.createElement(Button, { key: "reset" }, locale.reset), /*#__PURE__*/ React.createElement(Button, { key: "rotate" }, locale.avatarCropper.rotate), /*#__PURE__*/ React.createElement(Button, { type: "success", key: "confirm" }, locale.confirm) ]; defaultProps.editText = locale.edit; var _ref = _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 = _object_without_properties(_ref, [ "children", "toolbar", "maxZoom", "space", "toolbarPosition", "editText", "sizeType", "sourceType", "shape", "className", "style", "onConfirm", "onCancel" ]); var cls = classNames(classPrefix, 'taro', className, shape === 'round' && 'round'); var toolbarPositionCls = classNames("".concat(classPrefix, "-popup-toolbar"), toolbarPosition); var _useState = _sliced_to_array(useState(false), 2), visible = _useState[0], setVisible = _useState[1]; var _useState1 = _sliced_to_array(useState(false), 2), moving = _useState1[0], setMoving = _useState1[1]; var _useState2 = _sliced_to_array(useState(false), 2), zooming = _useState2[0], setZooming = _useState2[1]; var systemInfo = getWindowInfo(); // 支付宝基础库2.7.0以上支持,需要开启支付宝小程序canvas2d var showAlipayCanvas2D = useMemo(function() { return Taro.getEnv() === 'ALIPAY' && parseInt(Taro.SDKVersion.replace(/\./g, '')) >= 270; }, []); var showPixelRatio = Taro.getEnv() === 'WEB' || showAlipayCanvas2D; // 设备像素比 var pixelRatio = showPixelRatio ? systemInfo.pixelRatio : 1; var _useState3 = _sliced_to_array(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 = _sliced_to_array(useState(_object_spread({}, defDrawImage)), 2), drawImage = _useState4[0], setDrawImg = _useState4[1]; var _useState5 = _sliced_to_array(useState({ canvasId: "canvas-".concat(Date.now()), cropperCanvas: null, cropperCanvasContext: null }), 2), canvasAll = _useState5[0], setCanvasAll = _useState5[1]; useReady(function() { if (showAlipayCanvas2D) { var canvasId = canvasAll.canvasId; createSelectorQuery().select("#".concat(canvasId)).node(function(param) { var canvas = param.node; canvas.width = state.displayWidth; canvas.height = state.displayHeight; setCanvasAll(_object_spread_props(_object_spread({}, canvasAll), { cropperCanvas: canvas })); }).exec(); } }); useEffect(function() { setCanvasAll(_object_spread_props(_object_spread({}, canvasAll), { cropperCanvasContext: Taro.createCanvasContext(canvasAll.canvasId) })); }, []); var touch = useTouch(); var highlightStyle = 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 = useMemo(function() { return state.angle === 90 || state.angle === 270; }, [ state.angle ]); var canvasStyle = useMemo(function() { return { width: "".concat(state.displayWidth / pixelRatio, "px"), height: "".concat(state.displayHeight / pixelRatio, "px") }; }, [ pixelRatio, state.displayHeight, state.displayWidth ]); // 最大横向移动距离 var maxMoveX = 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 = 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(dataURL) { return new Promise(function(resolve) { var img = new Image(); img.onload = function() { return resolve(img); }; img.src = dataURL; }); }; // base64转图片(canvasImage) var dataURLToCanvasImage = function(canvas, dataURL) { return new Promise(function(resolve) { var img = canvas.createImage(); img.onload = function() { return resolve(img); }; img.src = dataURL; }); }; var canvas2dDraw = 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 = 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 = useCallback(function() { var ctx = canvasAll.cropperCanvas.getContext('2d'); ctx && ctx.resetTransform(); canvas2dDraw(ctx); }, [ canvas2dDraw, canvasAll.cropperCanvas ]); // 绘制显示的canvas内容 var draw = useCallback(function() { if (Taro.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 ]); useEffect(function() { if (Math.abs(state.moveX) > maxMoveX) { setState(_object_spread_props(_object_spread({}, state), { moveX: maxMoveX })); } if (Math.abs(state.moveY) > maxMoveY) { setState(_object_spread_props(_object_spread({}, state), { moveY: maxMoveY })); } draw(); }, [ state, maxMoveX, maxMoveY, draw ]); // 设置绘制图片 var setDrawImgInfo = function(image) { return _async_to_generator(function() { var displayWidth, cropperWidth, copyDrawImg, imgWidth, imgHeight, isPortrait, rate, scale; return _ts_generator(this, function(_state) { switch(_state.label){ case 0: displayWidth = state.displayWidth, cropperWidth = state.cropperWidth; copyDrawImg = _object_spread({}, defDrawImage); imgWidth = image.width, imgHeight = image.height; copyDrawImg.src = image.path; if (!(Taro.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(_object_spread_props(_object_spread({}, state), { defScale: scale })); resetScale(scale); return [ 2 ]; } }); })(); }; var chooseImage = function() { Taro.chooseImage({ count: 1, // 可以指定是原图还是压缩图,默认二者都有 sizeType: sizeType, sourceType: sourceType, success: function(res) { // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片 var tempFiles = res.tempFiles; !!tempFiles.length && imageChange(tempFiles[0]); } }); }; var imageChange = function(file) { return _async_to_generator(function() { return _ts_generator(this, function(_state) { Taro.getImageInfo({ src: file.path }).then(function(res) { setVisible(true); setDrawImgInfo(res); }); return [ 2 ]; }); })(); }; var resetScale = function(scale) { setState(_object_spread_props(_object_spread({}, state), { moveX: 0, moveY: 0, angle: 0, scale: scale || state.defScale, defScale: scale || state.defScale })); }; var setScale = function(scale) { scale = clamp(scale, +0.3, +maxZoom + 1); if (scale !== state.scale) { setState(_object_spread_props(_object_spread({}, state), { scale: scale })); } }; // 计算两个点的距离 var getDistance = function(touches) { return Math.sqrt(Math.pow(touches[0].clientX - touches[1].clientX, 2) + Math.pow(touches[0].clientY - touches[1].clientY, 2)); }; var _useState6 = _sliced_to_array(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(event) { var touches = event.touches; var offsetX = touch.offsetX; touch.start(event); var fingerNum = touches === null || touches === void 0 ? void 0 : touches.length; setStartMove(_object_spread_props(_object_spread({}, startMove), { startMoveX: state.moveX, startMoveY: state.moveY })); setMoving(fingerNum === 1); setZooming(fingerNum === 2 && !offsetX.current); if (fingerNum === 2 && !offsetX.current) { setStartMove(_object_spread_props(_object_spread({}, startMove), { startScale: state.scale, startDistance: getDistance(event.touches) })); } }; var onTouchMove = function(event) { var touches = event.touches; touch.move(event); if (moving || zooming) { 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(_object_spread_props(_object_spread({}, state), { moveX: clamp(moveX, -maxMoveX, maxMoveX), moveY: clamp(moveY, -maxMoveY, maxMoveY) })); } if (zooming && touches.length === 2) { var distance = getDistance(touches); var scale = startScale * distance / startDistance; setScale(scale); } }; var onTouchEnd = function(event) { var stopPropagation = false; if (moving || zooming) { stopPropagation = !(moving && startMoveX === state.moveX && startMoveY === state.moveY); if (!event.touches.length) { if (zooming) { setState(_object_spread_props(_object_spread({}, state), { moveX: clamp(state.moveX, -maxMoveX, maxMoveX), moveY: clamp(state.moveY, -maxMoveY, maxMoveY) })); setZooming(false); } setMoving(false); setStartMove(_object_spread_props(_object_spread({}, startMove), { startMoveX: 0, startMoveY: 0, startScale: state.defScale })); if (state.scale < state.defScale) { resetScale(); } if (state.scale > maxZoom) { setState(_object_spread_props(_object_spread({}, state), { scale: +maxZoom })); } } } preventDefault(event, stopPropagation); touch.reset(); }; var reset = function() { setState(_object_spread_props(_object_spread({}, state), { angle: 0 })); }; var rotate = function() { if (state.angle === 270) { setState(_object_spread_props(_object_spread({}, state), { angle: 0 })); return; } setState(_object_spread_props(_object_spread({}, state), { angle: state.angle + 90 })); }; var cancel = function() { var isEmit = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : true; setVisible(false); resetScale(); isEmit && onCancel && onCancel(); }; // web裁剪图片 var confirmWEB = function() { 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() { var cropperWidth = state.cropperWidth, displayHeight = state.displayHeight; var cropperCanvas = canvasAll.cropperCanvas; Taro.canvasToTempFilePath({ canvas: cropperCanvas, x: props.space, y: (displayHeight - cropperWidth) / 2, width: cropperWidth, height: cropperWidth, destWidth: cropperWidth, destHeight: cropperWidth, success: function(res) { return _async_to_generator(function() { var filePath; return _ts_generator(this, function(_state) { filePath = res.tempFilePath; onConfirm && onConfirm(filePath); cancel(false); return [ 2 ]; }); })(); } }); }; // 裁剪图片 var confirm = function() { if (Taro.getEnv() === 'WEB') { confirmWEB(); return; } if (showAlipayCanvas2D) { confirmALIPAY(); return; } var cropperWidth = state.cropperWidth, displayHeight = state.displayHeight; var canvasId = canvasAll.canvasId; // 将编辑后的canvas内容转成图片 Taro.canvasToTempFilePath({ canvasId: canvasId, x: props.space, y: (displayHeight - cropperWidth) / 2, width: cropperWidth, height: cropperWidth, destWidth: cropperWidth * systemInfo.pixelRatio, destHeight: cropperWidth * systemInfo.pixelRatio, success: function(res) { return _async_to_generator(function() { var filePath; return _ts_generator(this, function(_state) { filePath = res.tempFilePath; onConfirm && onConfirm(filePath); cancel(false); return [ 2 ]; }); })(); } }); }; var ToolBar = function() { var actions = [ cancel, reset, rotate, confirm ]; return /*#__PURE__*/ React.createElement(View, { className: "".concat(classPrefix, "-popup-toolbar-flex") }, actions.map(function(action, index) { return /*#__PURE__*/ React.createElement(View, { key: index, className: "".concat(classPrefix, "-popup-toolbar-item"), onClick: function(_e) { return action(); } }, toolbar[index]); })); }; var CropperPopup = function() { var canvasId = canvasAll.canvasId; return /*#__PURE__*/ React.createElement(View, { className: "".concat(classPrefix, "-popup"), style: { display: visible ? 'block' : 'none' } }, /*#__PURE__*/ React.createElement(Canvas, { id: canvasId, "canvas-id": canvasId, type: showAlipayCanvas2D ? '2d' : undefined, style: canvasStyle, className: "".concat(classPrefix, "-popup-canvas") }), /*#__PURE__*/ React.createElement(View, { className: "".concat(classPrefix, "-popup-highlight"), onTouchStart: onTouchStart, onTouchMove: onTouchMove, onTouchEnd: onTouchEnd }, /*#__PURE__*/ React.createElement(View, { className: "highlight", style: highlightStyle })), /*#__PURE__*/ React.createElement(View, { className: toolbarPositionCls }, /*#__PURE__*/ React.createElement(ToolBar, null))); }; return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement(View, _object_spread_props(_object_spread({ className: cls }, rest), { style: style }), children, /*#__PURE__*/ React.createElement(View, { className: "nut-avatar-cropper-edit-text", onClick: chooseImage }, editText)), CropperPopup()); }; AvatarCropper.displayName = 'NutAvatarCropper';