@nutui/nutui-react
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
468 lines (467 loc) • 18.3 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, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import classNames from "classnames";
import Button from "../button";
import { ComponentDefaults } from "../../utils/typings";
import { useTouch } from "../../hooks/use-touch";
import { clamp, preventDefault } from "../../utils";
import { getRect } from "../../utils/get-rect";
import { useConfig } from "../configprovider";
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',
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)
];
var _ref = _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 = _object_without_properties(_ref, [
"children",
"maxZoom",
"space",
"toolbar",
"toolbarPosition",
"editText",
"shape",
"className",
"style",
"onConfirm",
"onCancel"
]);
var cls = classNames(classPrefix, className, shape === 'round' && 'round');
var toolbarPositionCls = classNames("".concat(classPrefix, "-popup-toolbar"), toolbarPosition);
var inputImageRef = useRef(null);
var cropperPopupRef = useRef(null);
var canvasRef = useRef(null);
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 _useState3 = _sliced_to_array(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 = _sliced_to_array(useState(_object_spread({}, defDrawImage)), 2), drawImage = _useState4[0], setDrawImg = _useState4[1];
var devicePixelRatio = window.devicePixelRatio || 1;
var touch = useTouch();
var highlightStyle = 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 = useMemo(function() {
return state.angle === 90 || state.angle === 270;
}, [
state.angle
]);
// 最大横向移动距离
var maxMoveX = 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 = 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(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(dataURL) {
return new Promise(function(resolve) {
var img = new Image();
img.onload = function() {
return resolve(img);
};
img.src = dataURL;
});
};
// 绘制
var draw = 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
]);
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 setDrawImgs = function(image) {
var rect = 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 = _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(_object_spread_props(_object_spread({}, state), {
defScale: scale
}));
resetScale(scale);
};
var inputImageChange = /*#__PURE__*/ function() {
var _ref = _async_to_generator(function(event) {
var $el, files, base64, image;
return _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(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 _useState5 = _sliced_to_array(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(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();
inputImageRef.current && (inputImageRef.current.value = '');
isEmit && onCancel && onCancel();
};
// 裁剪图片
var confirm = function() {
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() {
var actions = [
cancel,
reset,
rotate,
confirm
];
return /*#__PURE__*/ React.createElement("div", {
className: "".concat(classPrefix, "-popup-toolbar-flex")
}, actions.map(function(action, index) {
return /*#__PURE__*/ React.createElement("div", {
key: index,
className: "".concat(classPrefix, "-popup-toolbar-item"),
onClick: function(_e) {
return action();
}
}, toolbar[index]);
}));
};
var CropperPopup = function() {
return /*#__PURE__*/ React.createElement("div", {
ref: cropperPopupRef,
className: "".concat(classPrefix, "-popup"),
style: {
display: visible ? 'block' : 'none'
}
}, /*#__PURE__*/ React.createElement("canvas", {
ref: canvasRef,
className: "".concat(classPrefix, "-popup-canvas")
}), /*#__PURE__*/ React.createElement("div", {
className: "".concat(classPrefix, "-popup-highlight"),
onTouchStart: onTouchStart,
onTouchMove: onTouchMove,
onTouchEnd: onTouchEnd
}, /*#__PURE__*/ React.createElement("div", {
className: "highlight",
style: highlightStyle
})), /*#__PURE__*/ React.createElement("div", {
className: toolbarPositionCls
}, /*#__PURE__*/ React.createElement(ToolBar, null)));
};
return /*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("div", _object_spread_props(_object_spread({
className: cls
}, rest), {
style: style
}), children, /*#__PURE__*/ React.createElement("input", {
ref: inputImageRef,
type: "file",
accept: "image/*",
className: "".concat(classPrefix, "-input"),
onChange: function(e) {
return inputImageChange(e);
},
"aria-label": locale.avatarCropper.selectImage
}), /*#__PURE__*/ React.createElement("div", {
className: "nut-avatar-cropper-edit-text"
}, editText)), CropperPopup());
};
AvatarCropper.displayName = 'NutAvatarCropper';