@nutui/nutui-react-taro
Version:
京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序
429 lines (428 loc) • 17.5 kB
JavaScript
import { _ as __rest, a as __awaiter } from "./tslib.es6-iWu3F_1J.js";
import React__default, { useState, useMemo, useEffect, useCallback } from "react";
import Taro, { useReady, createSelectorQuery } from "@tarojs/taro";
import classNames from "classnames";
import { Canvas } from "@tarojs/components";
import { g as getWindowInfo } from "./get-system-info-D27xNglo.js";
import { B as Button } from "./button.taro-CixO32Hw.js";
import { u as useConfig } from "./configprovider.taro-DpK4IiCE.js";
import { C as ComponentDefaults } from "./typings-DV9RBfhj.js";
import { u as useTouch } from "./use-touch-BeiLHDO9.js";
import { p as preventDefault, c as clamp } from "./index-DeNfBlua.js";
const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { maxZoom: 3, space: 10, toolbar: [
React__default.createElement(Button, { type: "danger", key: "cancel" }, "Cancel"),
React__default.createElement(Button, { key: "reset" }, "Reset"),
React__default.createElement(Button, { key: "rotate" }, "Rotate"),
React__default.createElement(Button, { type: "success", key: "confirm" }, "Confirm")
], toolbarPosition: "bottom", editText: "Edit", sizeType: ["original", "compressed"], sourceType: ["album", "camera"], shape: "square" });
const classPrefix = `nut-avatar-cropper`;
const AvatarCropper = (props) => {
const { locale } = useConfig();
defaultProps.toolbar = [
React__default.createElement(Button, { type: "danger", key: "cancel" }, locale.cancel),
React__default.createElement(Button, { key: "reset" }, locale.reset),
React__default.createElement(Button, { key: "rotate" }, locale.avatarCropper.rotate),
React__default.createElement(Button, { type: "success", key: "confirm" }, locale.confirm)
];
defaultProps.editText = locale.edit;
const _a = Object.assign(Object.assign({}, defaultProps), props), { children, toolbar, maxZoom, space, toolbarPosition, editText, sizeType, sourceType, shape, className, style, onConfirm, onCancel } = _a, rest = __rest(_a, ["children", "toolbar", "maxZoom", "space", "toolbarPosition", "editText", "sizeType", "sourceType", "shape", "className", "style", "onConfirm", "onCancel"]);
const cls = classNames(classPrefix, "taro", className, shape === "round" && "round");
const toolbarPositionCls = classNames(`${classPrefix}-popup-toolbar`, toolbarPosition);
const [visible, setVisible] = useState(false);
const [moving, setMoving] = useState(false);
const [zooming, setZooming] = useState(false);
const systemInfo = getWindowInfo();
const showAlipayCanvas2D = useMemo(() => {
return Taro.getEnv() === "ALIPAY" && parseInt(Taro.SDKVersion.replace(/\./g, "")) >= 270;
}, []);
const showPixelRatio = Taro.getEnv() === "WEB" || showAlipayCanvas2D;
const pixelRatio = showPixelRatio ? systemInfo.pixelRatio : 1;
const [state, setState] = 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
});
const defDrawImage = {
src: "",
// 规定要使用的图像
x: 0,
// 在画布上x的坐标位置
y: 0,
// 在画布上y的坐标位置
width: 0,
// 要使用的图像的宽度
height: 0
// 要使用的图像的高度
};
const [drawImage, setDrawImg] = useState(Object.assign({}, defDrawImage));
const [canvasAll, setCanvasAll] = useState({
canvasId: `canvas-${Date.now()}`,
cropperCanvas: null,
cropperCanvasContext: null
});
useReady(() => {
if (showAlipayCanvas2D) {
const { canvasId } = canvasAll;
createSelectorQuery().select(`#${canvasId}`).node(({ node: canvas }) => {
canvas.width = state.displayWidth;
canvas.height = state.displayHeight;
setCanvasAll(Object.assign(Object.assign({}, canvasAll), { cropperCanvas: canvas }));
}).exec();
}
});
useEffect(() => {
setCanvasAll(Object.assign(Object.assign({}, canvasAll), { cropperCanvasContext: Taro.createCanvasContext(canvasAll.canvasId) }));
}, []);
const touch = useTouch();
const highlightStyle = useMemo(() => {
const width = `${state.cropperWidth / pixelRatio}px`;
const height = width;
return {
width,
height,
borderRadius: shape === "round" ? "50%" : ""
};
}, [pixelRatio, state.cropperWidth]);
const isAngle = useMemo(() => {
return state.angle === 90 || state.angle === 270;
}, [state.angle]);
const canvasStyle = useMemo(() => {
return {
width: `${state.displayWidth / pixelRatio}px`,
height: `${state.displayHeight / pixelRatio}px`
};
}, [pixelRatio, state.displayHeight, state.displayWidth]);
const maxMoveX = useMemo(() => {
const { displayWidth, scale, cropperWidth } = state;
if (isAngle) {
return Math.max(0, (drawImage.height * scale - cropperWidth) / 2);
}
return Math.max(0, (displayWidth * scale - cropperWidth) / 2);
}, [drawImage.height, isAngle, state]);
const maxMoveY = useMemo(() => {
const { displayWidth, scale, cropperWidth } = state;
if (isAngle) {
return Math.max(0, (displayWidth * scale - cropperWidth) / 2);
}
return Math.max(0, (drawImage.height * scale - cropperWidth) / 2);
}, [drawImage.height, isAngle, state]);
const dataURLToImage = (dataURL) => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.src = dataURL;
});
};
const dataURLToCanvasImage = (canvas, dataURL) => {
return new Promise((resolve) => {
const img = canvas.createImage();
img.onload = () => resolve(img);
img.src = dataURL;
});
};
const canvas2dDraw = useCallback((ctx) => {
const { src, width, height, x, y } = drawImage;
if (!ctx || !src)
return;
const { moveX, moveY, scale, angle, displayWidth, displayHeight, cropperWidth } = state;
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]);
const webDraw = useCallback(() => {
const canvasDom = document.getElementById(canvasAll.canvasId);
let 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;
const ctx = canvas.getContext("2d");
canvas2dDraw(ctx);
}, [canvas2dDraw]);
const alipayDraw = useCallback(() => {
const ctx = canvasAll.cropperCanvas.getContext("2d");
ctx && ctx.resetTransform();
canvas2dDraw(ctx);
}, [canvas2dDraw, canvasAll.cropperCanvas]);
const draw = useCallback(() => {
if (Taro.getEnv() === "WEB") {
webDraw();
return;
}
if (showAlipayCanvas2D) {
alipayDraw();
return;
}
const { src, width, height, x, y } = drawImage;
const { moveX, moveY, scale, angle, displayWidth, displayHeight, cropperWidth } = state;
const { cropperCanvasContext } = canvasAll;
const 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(() => {
if (Math.abs(state.moveX) > maxMoveX) {
setState(Object.assign(Object.assign({}, state), { moveX: maxMoveX }));
}
if (Math.abs(state.moveY) > maxMoveY) {
setState(Object.assign(Object.assign({}, state), { moveY: maxMoveY }));
}
draw();
}, [state, maxMoveX, maxMoveY, draw]);
const setDrawImgInfo = (image) => __awaiter(void 0, void 0, void 0, function* () {
const { displayWidth, cropperWidth } = state;
const copyDrawImg = Object.assign({}, defDrawImage);
const { width: imgWidth, height: imgHeight } = image;
copyDrawImg.src = image.path;
if (Taro.getEnv() === "WEB") {
copyDrawImg.src = yield dataURLToImage(image.path);
}
if (showAlipayCanvas2D) {
copyDrawImg.src = yield dataURLToCanvasImage(canvasAll.cropperCanvas, image.path);
}
const isPortrait = imgHeight > imgWidth;
const 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);
const scale = cropperWidth / (isPortrait ? copyDrawImg.width : copyDrawImg.height);
setState(Object.assign(Object.assign({}, state), { defScale: scale }));
resetScale(scale);
});
const chooseImage = () => {
Taro.chooseImage({
count: 1,
// 可以指定是原图还是压缩图,默认二者都有
sizeType,
sourceType,
success: (res) => {
const { tempFiles } = res;
!!tempFiles.length && imageChange(tempFiles[0]);
}
});
};
const imageChange = (file) => __awaiter(void 0, void 0, void 0, function* () {
Taro.getImageInfo({
src: file.path
}).then((res) => {
setVisible(true);
setDrawImgInfo(res);
});
});
const resetScale = (scale) => {
setState(Object.assign(Object.assign({}, state), { moveX: 0, moveY: 0, angle: 0, scale: scale || state.defScale, defScale: scale || state.defScale }));
};
const setScale = (scale) => {
scale = clamp(scale, 0.3, +maxZoom + 1);
if (scale !== state.scale) {
setState(Object.assign(Object.assign({}, state), { scale }));
}
};
const getDistance = (touches) => Math.sqrt(Math.pow(touches[0].clientX - touches[1].clientX, 2) + Math.pow(touches[0].clientY - touches[1].clientY, 2));
const [startMove, setStartMove] = useState({
startMoveX: 0,
startMoveY: 0,
startScale: 0,
startDistance: 0
});
const { startMoveX, startMoveY, startScale, startDistance } = startMove;
const onTouchStart = (event) => {
const { touches } = event;
const { offsetX } = touch;
touch.start(event);
const fingerNum = touches === null || touches === void 0 ? void 0 : touches.length;
setStartMove(Object.assign(Object.assign({}, startMove), { startMoveX: state.moveX, startMoveY: state.moveY }));
setMoving(fingerNum === 1);
setZooming(fingerNum === 2 && !offsetX.current);
if (fingerNum === 2 && !offsetX.current) {
setStartMove(Object.assign(Object.assign({}, startMove), { startScale: state.scale, startDistance: getDistance(event.touches) }));
}
};
const onTouchMove = (event) => {
const { touches } = event;
touch.move(event);
if (moving || zooming) {
preventDefault(event, true);
}
if (moving) {
const { deltaX, deltaY } = touch;
const moveX = deltaX.current * state.scale + startMoveX;
const moveY = deltaY.current * state.scale + startMoveY;
setState(Object.assign(Object.assign({}, state), { moveX: clamp(moveX, -maxMoveX, maxMoveX), moveY: clamp(moveY, -maxMoveY, maxMoveY) }));
}
if (zooming && touches.length === 2) {
const distance = getDistance(touches);
const scale = startScale * distance / startDistance;
setScale(scale);
}
};
const onTouchEnd = (event) => {
let stopPropagation = false;
if (moving || zooming) {
stopPropagation = !(moving && startMoveX === state.moveX && startMoveY === state.moveY);
if (!event.touches.length) {
if (zooming) {
setState(Object.assign(Object.assign({}, state), { moveX: clamp(state.moveX, -maxMoveX, maxMoveX), moveY: clamp(state.moveY, -maxMoveY, maxMoveY) }));
setZooming(false);
}
setMoving(false);
setStartMove(Object.assign(Object.assign({}, startMove), { startMoveX: 0, startMoveY: 0, startScale: state.defScale }));
if (state.scale < state.defScale) {
resetScale();
}
if (state.scale > maxZoom) {
setState(Object.assign(Object.assign({}, state), { scale: +maxZoom }));
}
}
}
preventDefault(event, stopPropagation);
touch.reset();
};
const reset = () => {
setState(Object.assign(Object.assign({}, state), { angle: 0 }));
};
const rotate = () => {
if (state.angle === 270) {
setState(Object.assign(Object.assign({}, state), { angle: 0 }));
return;
}
setState(Object.assign(Object.assign({}, state), { angle: state.angle + 90 }));
};
const cancel = (isEmit = true) => {
setVisible(false);
resetScale();
isEmit && onCancel && onCancel();
};
const confirmWEB = () => {
const { cropperWidth, displayHeight } = state;
const canvasDom = document.getElementById(canvasAll.canvasId);
let 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];
}
const width = cropperWidth;
const height = cropperWidth;
const croppedCanvas = document.createElement("canvas");
const croppedCtx = croppedCanvas.getContext("2d");
croppedCanvas.width = width;
croppedCanvas.height = height;
canvas && croppedCtx.drawImage(canvas, space * pixelRatio, (displayHeight - cropperWidth) / 2, width, height, 0, 0, width, height);
const imageDataURL = croppedCanvas.toDataURL("image/png");
onConfirm && onConfirm(imageDataURL);
cancel(false);
};
const confirmALIPAY = () => {
const { cropperWidth, displayHeight } = state;
const { cropperCanvas } = canvasAll;
Taro.canvasToTempFilePath({
canvas: cropperCanvas,
x: props.space,
y: (displayHeight - cropperWidth) / 2,
width: cropperWidth,
height: cropperWidth,
destWidth: cropperWidth,
destHeight: cropperWidth,
success: (res) => __awaiter(void 0, void 0, void 0, function* () {
const filePath = res.tempFilePath;
onConfirm && onConfirm(filePath);
cancel(false);
})
});
};
const confirm = () => {
if (Taro.getEnv() === "WEB") {
confirmWEB();
return;
}
if (showAlipayCanvas2D) {
confirmALIPAY();
return;
}
const { cropperWidth, displayHeight } = state;
const { canvasId } = canvasAll;
Taro.canvasToTempFilePath({
canvasId,
x: props.space,
y: (displayHeight - cropperWidth) / 2,
width: cropperWidth,
height: cropperWidth,
destWidth: cropperWidth * systemInfo.pixelRatio,
destHeight: cropperWidth * systemInfo.pixelRatio,
success: (res) => __awaiter(void 0, void 0, void 0, function* () {
const filePath = res.tempFilePath;
onConfirm && onConfirm(filePath);
cancel(false);
})
});
};
const ToolBar = () => {
const actions = [cancel, reset, rotate, confirm];
return React__default.createElement("div", { className: `${classPrefix}-popup-toolbar-flex` }, actions.map((action, index) => React__default.createElement("div", { key: index, className: `${classPrefix}-popup-toolbar-item`, onClick: (_e) => action() }, toolbar[index])));
};
const CropperPopup = () => {
const { canvasId } = canvasAll;
return React__default.createElement(
"div",
{ className: `${classPrefix}-popup`, style: { display: visible ? "block" : "none" } },
React__default.createElement(Canvas, { id: canvasId, "canvas-id": canvasId, type: showAlipayCanvas2D ? "2d" : void 0, style: canvasStyle, className: `${classPrefix}-popup-canvas` }),
React__default.createElement(
"div",
{ className: `${classPrefix}-popup-highlight`, onTouchStart, onTouchMove, onTouchEnd },
React__default.createElement("div", { className: "highlight", style: highlightStyle })
),
React__default.createElement(
"div",
{ className: toolbarPositionCls },
React__default.createElement(ToolBar, null)
)
);
};
return React__default.createElement(
React__default.Fragment,
null,
React__default.createElement(
"div",
Object.assign({ className: cls }, rest, { style }),
children,
React__default.createElement("div", { className: "nut-avatar-cropper-edit-text", onClick: chooseImage }, editText)
),
CropperPopup()
);
};
AvatarCropper.displayName = "NutAvatarCropper";
export {
AvatarCropper as A
};