@nutui/nutui-react-taro
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
651 lines (650 loc) • 25.9 kB
JavaScript
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';