icssoa 8 месяцев назад
Родитель
Сommit
4c5677e75f
2 измененных файлов с 461 добавлено и 242 удалено
  1. 24 47
      pages/demo/other/cropper.uvue
  2. 437 195
      uni_modules/cool-ui/components/cl-cropper/cl-cropper.uvue

+ 24 - 47
pages/demo/other/cropper.uvue

@@ -1,47 +1,17 @@
 <template>
 	<cl-page>
-		<view class="p-4">
-			<cl-text size="lg" bold class="mb-4">图片裁剪器演示</cl-text>
-
-			<view class="mb-4">
-				<cl-text size="sm" color="info" class="mb-2">操作说明:</cl-text>
-				<cl-text size="xs" color="placeholder" class="block mb-1"
-					>• 拖拽图片进行移动</cl-text
-				>
-				<cl-text size="xs" color="placeholder" class="block mb-1"
-					>• 双指缩放图片(围绕双指中心缩放)</cl-text
-				>
-				<cl-text size="xs" color="placeholder" class="block mb-1"
-					>• 拖拽裁剪框进行移动</cl-text
-				>
-				<cl-text size="xs" color="placeholder" class="block mb-1"
-					>• 拖拽裁剪框四角进行缩放</cl-text
-				>
-				<cl-text size="xs" color="placeholder" class="block mb-1"
-					>• 缩放时会显示辅助线</cl-text
-				>
-				<cl-text size="xs" color="warning" class="block"
-					>• 图片会自动保持不小于裁剪框大小</cl-text
-				>
-			</view>
-
-			<view class="flex justify-center">
-				<cl-cropper
-					:src="imageSrc"
-					:width="320"
-					:height="320"
-					:crop-width="200"
-					:crop-height="200"
-					:max-scale="3"
-					:min-scale="0.5"
-					@crop="onCrop"
-					@load="onImageLoad"
-				></cl-cropper>
-			</view>
-
-			<view class="mt-4 space-y-2">
-				<cl-button @tap="selectImage" block>选择图片</cl-button>
-				<cl-button @tap="useDefaultImage" type="light" block>使用默认图片</cl-button>
+		<view class="cropper-demo">
+			<cl-cropper
+				:src="imageSrc"
+				:max-scale="3"
+				:min-scale="0.5"
+				@crop="onCrop"
+				@load="onImageLoad"
+			></cl-cropper>
+			
+			<!-- 选择图片按钮 -->
+			<view class="select-image-btn" @tap="selectImage">
+				<cl-button type="primary" size="small">选择图片</cl-button>
 			</view>
 		</view>
 	</cl-page>
@@ -50,7 +20,7 @@
 <script lang="ts" setup>
 import { ref } from "vue";
 
-const imageSrc = ref("/static/logo.png");
+const imageSrc = ref("https://unix.cool-js.com/images/demo/avatar.jpg");
 
 function selectImage() {
 	// 选择图片
@@ -66,10 +36,6 @@ function selectImage() {
 	});
 }
 
-function useDefaultImage() {
-	imageSrc.value = "/static/logo.png";
-}
-
 function onCrop(result: any) {
 	console.log("裁剪结果:", result);
 	uni.showToast({
@@ -82,3 +48,14 @@ function onImageLoad(e: any) {
 	console.log("图片加载完成:", e);
 }
 </script>
+
+<style lang="scss" scoped>
+.cropper-demo {
+	@apply relative w-full h-full;
+}
+
+.select-image-btn {
+	@apply absolute top-4 right-4;
+	z-index: 10;
+}
+</style>

+ 437 - 195
uni_modules/cool-ui/components/cl-cropper/cl-cropper.uvue

@@ -7,16 +7,11 @@
 			},
 			pt.className
 		]"
-		:style="{
-			width: width + 'px',
-			height: height + 'px'
-		}"
 	>
 		<view class="cl-cropper__container" :class="[pt.inner?.className]">
 			<!-- 图片容器 -->
 			<view
 				class="cl-cropper__image-container"
-				:style="imageContainerStyle"
 				@touchstart="onImageTouchStart"
 				@touchmove="onImageTouchMove"
 				@touchend="onImageTouchEnd"
@@ -26,7 +21,6 @@
 					:class="[pt.image?.className]"
 					:src="src"
 					:style="imageStyle"
-					mode="aspectFit"
 					@load="onImageLoad"
 				></image>
 			</view>
@@ -46,9 +40,9 @@
 				<view
 					class="cl-cropper__crop-area"
 					:class="{ 'is-resizing': isResizing }"
-					@touchstart="onCropTouchStart"
-					@touchmove="onCropTouchMove"
-					@touchend="onCropTouchEnd"
+					@touchstart="onCropAreaTouchStart"
+					@touchmove="onCropAreaTouchMove"
+					@touchend="onCropAreaTouchEnd"
 				>
 					<!-- 辅助线 -->
 					<view class="cl-cropper__guide-lines" v-if="showGuideLines">
@@ -57,30 +51,30 @@
 						<view class="cl-cropper__guide-line cl-cropper__guide-line--v1"></view>
 						<view class="cl-cropper__guide-line cl-cropper__guide-line--v2"></view>
 					</view>
-					
+
 					<!-- 拖拽点 -->
-					<view 
+					<view
 						class="cl-cropper__drag-point cl-cropper__drag-point--tl"
 						@touchstart="onResizeTouchStart"
-						@touchmove="onResizeTouchMove" 
+						@touchmove="onResizeTouchMove"
 						@touchend="onResizeTouchEnd"
 						data-direction="tl"
 					></view>
-					<view 
+					<view
 						class="cl-cropper__drag-point cl-cropper__drag-point--tr"
 						@touchstart="onResizeTouchStart"
 						@touchmove="onResizeTouchMove"
 						@touchend="onResizeTouchEnd"
 						data-direction="tr"
 					></view>
-					<view 
+					<view
 						class="cl-cropper__drag-point cl-cropper__drag-point--bl"
 						@touchstart="onResizeTouchStart"
 						@touchmove="onResizeTouchMove"
 						@touchend="onResizeTouchEnd"
 						data-direction="bl"
 					></view>
-					<view 
+					<view
 						class="cl-cropper__drag-point cl-cropper__drag-point--br"
 						@touchstart="onResizeTouchStart"
 						@touchmove="onResizeTouchMove"
@@ -118,6 +112,48 @@ import { computed, ref, reactive, onMounted, type PropType } from "vue";
 import type { PassThroughProps } from "../../types";
 import { parsePt, parseRpx } from "@/cool";
 
+// 类型定义
+type RectType = {
+	height: number;
+	width: number;
+};
+
+type ImageInfoType = {
+	width: number;
+	height: number;
+	loaded: boolean;
+};
+
+type ImageTransformType = {
+	translateX: number;
+	translateY: number;
+};
+
+type ImageDisplayType = {
+	width: number;
+	height: number;
+};
+
+type CropBoxType = {
+	x: number;
+	y: number;
+	width: number;
+	height: number;
+};
+
+type TouchStateType = {
+	startX: number;
+	startY: number;
+	startDistance: number;
+	startWidth: number;
+	startHeight: number;
+	startTranslateX: number;
+	startTranslateY: number;
+	touching: boolean;
+	mode: string; // image, crop, resize
+	resizeDirection: string; // tl, tr, bl, br
+};
+
 defineOptions({
 	name: "cl-cropper"
 });
@@ -133,25 +169,15 @@ const props = defineProps({
 		type: String,
 		default: ""
 	},
-	// 容器宽度
-	width: {
-		type: [String, Number] as PropType<string | number>,
-		default: 375
-	},
-	// 容器高度
-	height: {
-		type: [String, Number] as PropType<string | number>,
-		default: 375
-	},
 	// 裁剪框宽度
 	cropWidth: {
-		type: [String, Number] as PropType<string | number>,
-		default: 200
+		type: Number,
+		default: 300
 	},
 	// 裁剪框高度
 	cropHeight: {
-		type: [String, Number] as PropType<string | number>,
-		default: 200
+		type: Number,
+		default: 300
 	},
 	// 最大缩放比例
 	maxScale: {
@@ -200,34 +226,47 @@ type PassThrough = {
 // 解析透传样式
 const pt = computed(() => parsePt<PassThrough>(props.pt));
 
+const { windowHeight, windowWidth } = uni.getWindowInfo();
+
+const rect = reactive<RectType>({
+	height: windowHeight,
+	width: windowWidth
+});
+
 // 图片信息
-const imageInfo = reactive({
+const imageInfo = reactive<ImageInfoType>({
 	width: 0,
 	height: 0,
 	loaded: false
 });
 
 // 图片变换状态
-const imageTransform = reactive({
-	scale: 1,
+const imageTransform = reactive<ImageTransformType>({
 	translateX: 0,
 	translateY: 0
 });
 
+// 图片显示尺寸
+const imageDisplay = reactive<ImageDisplayType>({
+	width: 0,
+	height: 0
+});
+
 // 裁剪框状态
-const cropBox = reactive({
+const cropBox = reactive<CropBoxType>({
 	x: 0,
 	y: 0,
-	width: 200,
-	height: 200
+	width: props.cropWidth,
+	height: props.cropHeight
 });
 
 // 触摸状态
-const touchState = reactive({
+const touchState = reactive<TouchStateType>({
 	startX: 0,
 	startY: 0,
 	startDistance: 0,
-	startScale: 1,
+	startWidth: 0,
+	startHeight: 0,
 	startTranslateX: 0,
 	startTranslateY: 0,
 	touching: false,
@@ -239,22 +278,24 @@ const touchState = reactive({
 const isResizing = ref(false);
 const showGuideLines = ref(false);
 
-// 计算图片容器样式
-const imageContainerStyle = computed(() => {
-	return {
-		width: "100%",
-		height: "100%",
-		overflow: "hidden"
-	};
-});
-
 // 计算图片样式
 const imageStyle = computed(() => {
-	return {
-		transform: `translate3d(${imageTransform.translateX}px, ${imageTransform.translateY}px, 0) scale(${imageTransform.scale})`,
-		transformOrigin: "center center",
-		transition: touchState.touching ? "none" : "transform 0.3s ease"
-	};
+	if (touchState.touching) {
+		return {
+			transform: `translate(${imageTransform.translateX}px, ${imageTransform.translateY}px)`,
+			transitionProperty: "none",
+			height: imageDisplay.height + "px",
+			width: imageDisplay.width + "px"
+		};
+	} else {
+		return {
+			transform: `translate(${imageTransform.translateX}px, ${imageTransform.translateY}px)`,
+			transitionProperty: "transform, width, height",
+			transitionDuration: "0.3s",
+			height: imageDisplay.height + "px",
+			width: imageDisplay.width + "px"
+		};
+	}
 });
 
 // 计算裁剪框样式
@@ -276,59 +317,108 @@ function onImageLoad(e: any) {
 	// 初始化裁剪框位置
 	initCropBox();
 
+	// 设置初始图片尺寸,确保图片按比例适配到裁剪框
+	setInitialSize();
+
+	// 检查边界,确保图片覆盖裁剪框
+	adjustImageBounds();
+
 	emit("load", e);
 }
 
-// 计算图片最小缩放比例(确保图片不小于裁剪框)
-function calculateMinScale() {
+// 设置初始图片尺寸,确保图片按比例适配到裁剪框
+function setInitialSize() {
+	if (!imageInfo.loaded || !imageInfo.width || !imageInfo.height) {
+		return;
+	}
+
+	console.log(imageInfo.height, imageInfo.width);
+
+	// 计算图片在容器中的基础显示尺寸
+	const containerWidth = rect.width;
+	const containerHeight = rect.height;
+	const imageAspectRatio = imageInfo.width / imageInfo.height;
+	const containerAspectRatio = containerWidth / containerHeight;
+
+	let baseDisplayWidth: number;
+	let baseDisplayHeight: number;
+
+	if (imageAspectRatio > containerAspectRatio) {
+		// 图片较宽,以容器宽度为准
+		baseDisplayWidth = containerWidth;
+		baseDisplayHeight = containerWidth / imageAspectRatio;
+	} else {
+		// 图片较高,以容器高度为准
+		baseDisplayHeight = containerHeight;
+		baseDisplayWidth = containerHeight * imageAspectRatio;
+	}
+
+	// 计算确保能覆盖裁剪框的最小尺寸
+	const minScaleForWidth = cropBox.width / baseDisplayWidth;
+	const minScaleForHeight = cropBox.height / baseDisplayHeight;
+	const minScale = Math.max(minScaleForWidth, minScaleForHeight);
+
+	// 设置图片显示尺寸
+	imageDisplay.width = baseDisplayWidth * minScale;
+	imageDisplay.height = baseDisplayHeight * minScale;
+}
+
+// 计算图片最小显示尺寸(确保图片尺寸不小于裁剪框)
+function calculateMinSize() {
 	if (!imageInfo.loaded || !imageInfo.width || !imageInfo.height) {
-		return props.minScale;
+		return { width: 0, height: 0 };
 	}
 
-	const containerWidth = parseFloat(parseRpx(props.width!).replace("px", ""));
-	const containerHeight = parseFloat(parseRpx(props.height!).replace("px", ""));
+	const containerWidth = rect.width;
+	const containerHeight = rect.height;
 
-	// 计算图片在容器中的显示尺寸(保持宽高比)
+	// 计算图片在容器中的基础显示尺寸(mode="aspectFit"的显示尺寸
 	const imageAspectRatio = imageInfo.width / imageInfo.height;
 	const containerAspectRatio = containerWidth / containerHeight;
 
-	let displayWidth: number;
-	let displayHeight: number;
+	let baseDisplayWidth: number;
+	let baseDisplayHeight: number;
 
 	if (imageAspectRatio > containerAspectRatio) {
 		// 图片较宽,以容器宽度为准
-		displayWidth = containerWidth;
-		displayHeight = containerWidth / imageAspectRatio;
+		baseDisplayWidth = containerWidth;
+		baseDisplayHeight = containerWidth / imageAspectRatio;
 	} else {
 		// 图片较高,以容器高度为准
-		displayHeight = containerHeight;
-		displayWidth = containerHeight * imageAspectRatio;
+		baseDisplayHeight = containerHeight;
+		baseDisplayWidth = containerHeight * imageAspectRatio;
 	}
 
-	// 计算使图片能够覆盖裁剪框的最小缩放比例
-	const minScaleX = cropBox.width / displayWidth;
-	const minScaleY = cropBox.height / displayHeight;
-	const calculatedMinScale = Math.max(minScaleX, minScaleY);
+	// 计算使图片尺寸能够完全覆盖裁剪框的最小尺寸
+	const minScaleForWidth = cropBox.width / baseDisplayWidth;
+	const minScaleForHeight = cropBox.height / baseDisplayHeight;
+	const minScale = Math.max(minScaleForWidth, minScaleForHeight);
+
+	// 应用用户设置的最小缩放限制
+	const finalScale = Math.max(props.minScale, minScale * 1.01); // 增加1%的缓冲
 
-	// 确保不低于用户设置的最小缩放比例
-	return Math.max(props.minScale, calculatedMinScale);
+	return {
+		width: baseDisplayWidth * finalScale,
+		height: baseDisplayHeight * finalScale
+	};
 }
 
 // 初始化裁剪框
 function initCropBox() {
-	const containerWidth = parseFloat(parseRpx(props.width!).replace("px", ""));
-	const containerHeight = parseFloat(parseRpx(props.height!).replace("px", ""));
+	const containerWidth = rect.width;
+	const containerHeight = rect.height;
 
-	cropBox.width = parseFloat(parseRpx(props.cropWidth!).replace("px", ""));
-	cropBox.height = parseFloat(parseRpx(props.cropHeight!).replace("px", ""));
+	cropBox.width = props.cropWidth;
+	cropBox.height = props.cropHeight;
 	cropBox.x = (containerWidth - cropBox.width) / 2;
 	cropBox.y = (containerHeight - cropBox.height) / 2;
 
-	// 图片加载完成后,确保当前缩放比例不小于最小要求
+	// 图片加载完成后,确保当前图片尺寸不小于最小要求
 	if (imageInfo.loaded) {
-		const minScale = calculateMinScale();
-		if (imageTransform.scale < minScale) {
-			imageTransform.scale = minScale;
+		const minSize = calculateMinSize();
+		if (imageDisplay.width < minSize.width || imageDisplay.height < minSize.height) {
+			imageDisplay.width = minSize.width;
+			imageDisplay.height = minSize.height;
 		}
 	}
 }
@@ -350,14 +440,15 @@ function onImageTouchStart(e: TouchEvent) {
 		// 双指缩放
 		const touch1 = e.touches[0];
 		const touch2 = e.touches[1];
-		
+
 		// 计算两指间距离
 		touchState.startDistance = Math.sqrt(
 			Math.pow(touch2.clientX - touch1.clientX, 2) +
 				Math.pow(touch2.clientY - touch1.clientY, 2)
 		);
-		touchState.startScale = imageTransform.scale;
-		
+		touchState.startWidth = imageDisplay.width;
+		touchState.startHeight = imageDisplay.height;
+
 		// 记录缩放中心点(两指中心)
 		touchState.startX = (touch1.clientX + touch2.clientX) / 2;
 		touchState.startY = (touch1.clientY + touch2.clientY) / 2;
@@ -377,42 +468,60 @@ function onImageTouchMove(e: TouchEvent) {
 		const deltaX = e.touches[0].clientX - touchState.startX;
 		const deltaY = e.touches[0].clientY - touchState.startY;
 
-		imageTransform.translateX = touchState.startTranslateX + deltaX;
-		imageTransform.translateY = touchState.startTranslateY + deltaY;
+		// 计算新位置
+		const newTranslateX = touchState.startTranslateX + deltaX;
+		const newTranslateY = touchState.startTranslateY + deltaY;
+
+		// 应用新位置(在移动过程中不做边界检查,等移动结束后再检查)
+		imageTransform.translateX = newTranslateX;
+		imageTransform.translateY = newTranslateY;
 	} else if (e.touches != null && e.touches.length == 2) {
 		// 双指缩放
 		const touch1 = e.touches[0];
 		const touch2 = e.touches[1];
-		
+
 		// 计算当前两指间距离
 		const distance = Math.sqrt(
 			Math.pow(touch2.clientX - touch1.clientX, 2) +
 				Math.pow(touch2.clientY - touch1.clientY, 2)
 		);
 
-		// 计算缩放比例
-		const scale = (distance / touchState.startDistance) * touchState.startScale;
-		
-		// 使用动态计算的最小缩放比例
-		const minScale = calculateMinScale();
-		const newScale = Math.max(minScale, Math.min(props.maxScale, scale));
-		
+		// 计算尺寸缩放倍数
+		const scaleFactor = distance / touchState.startDistance;
+
+		// 计算新的图片尺寸
+		const newWidth = touchState.startWidth * scaleFactor;
+		const newHeight = touchState.startHeight * scaleFactor;
+
+		// 检查尺寸限制
+		const minSize = calculateMinSize();
+		const containerWidth = rect.width;
+		const containerHeight = rect.height;
+		const maxWidth = containerWidth * props.maxScale;
+		const maxHeight = containerHeight * props.maxScale;
+
+		// 应用尺寸限制
+		const finalWidth = Math.max(minSize.width, Math.min(maxWidth, newWidth));
+		const finalHeight = Math.max(minSize.height, Math.min(maxHeight, newHeight));
+
 		// 计算当前缩放中心点
 		const centerX = (touch1.clientX + touch2.clientX) / 2;
 		const centerY = (touch1.clientY + touch2.clientY) / 2;
-		
+
 		// 计算缩放中心相对于容器的偏移
-		const containerWidth = parseFloat(parseRpx(props.width!).replace("px", ""));
-		const containerHeight = parseFloat(parseRpx(props.height!).replace("px", ""));
 		const containerCenterX = containerWidth / 2;
 		const containerCenterY = containerHeight / 2;
-		
-		// 缩放时调整位移,使缩放围绕双指中心进行
-		const scaleDelta = newScale - touchState.startScale;
-		const offsetX = (centerX - containerCenterX - touchState.startX + containerCenterX) * scaleDelta / touchState.startScale;
-		const offsetY = (centerY - containerCenterY - touchState.startY + containerCenterY) * scaleDelta / touchState.startScale;
-		
-		imageTransform.scale = newScale;
+
+		// 计算尺寸变化引起的位移调整
+		const widthDelta = finalWidth - touchState.startWidth;
+		const heightDelta = finalHeight - touchState.startHeight;
+
+		// 根据缩放中心调整位移
+		const offsetX = ((centerX - containerCenterX) * widthDelta) / (2 * touchState.startWidth);
+		const offsetY = ((centerY - containerCenterY) * heightDelta) / (2 * touchState.startHeight);
+
+		imageDisplay.width = finalWidth;
+		imageDisplay.height = finalHeight;
 		imageTransform.translateX = touchState.startTranslateX - offsetX;
 		imageTransform.translateY = touchState.startTranslateY - offsetY;
 	}
@@ -422,49 +531,43 @@ function onImageTouchMove(e: TouchEvent) {
 function onImageTouchEnd(e: TouchEvent) {
 	touchState.touching = false;
 	touchState.mode = "";
-}
-
-// 裁剪框触摸开始
-function onCropTouchStart(e: TouchEvent) {
-	if (props.disabled) return;
 
-	e.stopPropagation();
-	touchState.touching = true;
-	touchState.mode = "crop";
-
-	if (e.touches != null && e.touches.length == 1) {
-		touchState.startX = e.touches[0].clientX;
-		touchState.startY = e.touches[0].clientY;
-	}
+	// 检查并调整图片边界,确保覆盖整个裁剪框
+	adjustImageBounds();
 }
 
-// 裁剪框触摸移动
-function onCropTouchMove(e: TouchEvent) {
-	if (props.disabled || !touchState.touching || touchState.mode != "crop") return;
-
-	e.preventDefault();
-	e.stopPropagation();
+// 裁剪区域触摸开始 - 用于在裁剪框内拖拽图片
+function onCropAreaTouchStart(e: TouchEvent) {
+	// 如果触摸点在拖拽点上,不处理图片拖拽
+	const target = e.target as HTMLElement;
+	if (target.classList.contains("cl-cropper__drag-point")) {
+		return;
+	}
 
-	if (e.touches != null && e.touches.length == 1) {
-		const deltaX = e.touches[0].clientX - touchState.startX;
-		const deltaY = e.touches[0].clientY - touchState.startY;
+	// 调用图片拖拽逻辑
+	onImageTouchStart(e);
+}
 
-		const containerWidth = parseFloat(parseRpx(props.width!).replace("px", ""));
-		const containerHeight = parseFloat(parseRpx(props.height!).replace("px", ""));
+// 裁剪区域触摸移动 - 用于在裁剪框内拖拽图片
+function onCropAreaTouchMove(e: TouchEvent) {
+	// 如果不是图片拖拽模式,不处理
+	if (touchState.mode != "image") {
+		return;
+	}
 
-		// 限制裁剪框在容器内
-		cropBox.x = Math.max(0, Math.min(containerWidth - cropBox.width, cropBox.x + deltaX));
-		cropBox.y = Math.max(0, Math.min(containerHeight - cropBox.height, cropBox.y + deltaY));
+	// 调用图片拖拽逻辑
+	onImageTouchMove(e);
+}
 
-		touchState.startX = e.touches[0].clientX;
-		touchState.startY = e.touches[0].clientY;
+// 裁剪区域触摸结束 - 用于在裁剪框内拖拽图片
+function onCropAreaTouchEnd(e: TouchEvent) {
+	// 如果不是图片拖拽模式,不处理
+	if (touchState.mode != "image") {
+		return;
 	}
-}
 
-// 裁剪框触摸结束
-function onCropTouchEnd(e: TouchEvent) {
-	touchState.touching = false;
-	touchState.mode = "";
+	// 调用图片拖拽逻辑
+	onImageTouchEnd(e);
 }
 
 // 裁剪框缩放开始
@@ -472,7 +575,7 @@ function onResizeTouchStart(e: TouchEvent) {
 	if (props.disabled) return;
 
 	e.stopPropagation();
-	
+
 	touchState.touching = true;
 	touchState.mode = "resize";
 	isResizing.value = true;
@@ -499,12 +602,12 @@ function onResizeTouchMove(e: TouchEvent) {
 		const deltaX = e.touches[0].clientX - touchState.startX;
 		const deltaY = e.touches[0].clientY - touchState.startY;
 
-		const containerWidth = parseFloat(parseRpx(props.width!).replace("px", ""));
-		const containerHeight = parseFloat(parseRpx(props.height!).replace("px", ""));
+		const containerWidth = rect.width;
+		const containerHeight = rect.height;
 
 		// 最小裁剪框尺寸
 		const minSize = 50;
-		
+
 		let newX = cropBox.x;
 		let newY = cropBox.y;
 		let newWidth = cropBox.width;
@@ -537,21 +640,17 @@ function onResizeTouchMove(e: TouchEvent) {
 		// 确保尺寸不小于最小值且不超出容器
 		if (newWidth >= minSize && newHeight >= minSize) {
 			// 检查是否超出容器边界
-			if (newX >= 0 && newY >= 0 && 
-				newX + newWidth <= containerWidth && 
-				newY + newHeight <= containerHeight) {
-				
+			if (
+				newX >= 0 &&
+				newY >= 0 &&
+				newX + newWidth <= containerWidth &&
+				newY + newHeight <= containerHeight
+			) {
 				cropBox.x = newX;
 				cropBox.y = newY;
 				cropBox.width = newWidth;
 				cropBox.height = newHeight;
 
-				// 当裁剪框大小改变时,检查图片是否需要放大
-				const minScale = calculateMinScale();
-				if (imageTransform.scale < minScale) {
-					imageTransform.scale = minScale;
-				}
-
 				// 更新起始位置
 				touchState.startX = e.touches[0].clientX;
 				touchState.startY = e.touches[0].clientY;
@@ -566,7 +665,10 @@ function onResizeTouchEnd(e: TouchEvent) {
 	touchState.mode = "";
 	touchState.resizeDirection = "";
 	isResizing.value = false;
-	
+
+	// 拖拽结束后,自动调整裁剪框尺寸和图片缩放
+	adjustCropBoxToDefault();
+
 	// 延迟隐藏辅助线,给用户一点时间看到最终位置
 	setTimeout(() => {
 		showGuideLines.value = false;
@@ -575,19 +677,19 @@ function onResizeTouchEnd(e: TouchEvent) {
 
 // 重置
 function reset() {
-	// 重置位移
-	imageTransform.translateX = 0;
-	imageTransform.translateY = 0;
-	
 	// 重置裁剪框
 	initCropBox();
-	
-	// 设置合适的初始缩放比例(确保图片能覆盖裁剪框)
+
+	// 设置初始图片尺寸,确保图片按比例适配到裁剪框
 	if (imageInfo.loaded) {
-		const minScale = calculateMinScale();
-		imageTransform.scale = Math.max(1, minScale);
+		setInitialSize();
+		// 检查边界
+		adjustImageBounds();
 	} else {
-		imageTransform.scale = 1;
+		imageDisplay.width = 0;
+		imageDisplay.height = 0;
+		imageTransform.translateX = 0;
+		imageTransform.translateY = 0;
 	}
 }
 
@@ -599,17 +701,141 @@ function crop() {
 	}
 }
 
+// 调整裁剪框到默认尺寸(宽度恢复默认,高度按比例计算)
+function adjustCropBoxToDefault() {
+	// 记录调整前的状态
+	const oldWidth = cropBox.width;
+	const oldHeight = cropBox.height;
+	const oldImageWidth = imageDisplay.width;
+	const oldImageHeight = imageDisplay.height;
+
+	// 计算当前裁剪框的宽高比
+	const currentRatio = cropBox.width / cropBox.height;
+
+	// 设置宽度为默认值
+	const newWidth = props.cropWidth;
+	// 按当前比例计算新高度
+	const newHeight = newWidth / currentRatio;
+
+	const containerWidth = rect.width;
+	const containerHeight = rect.height;
+
+	// 确保新尺寸不超出容器
+	if (newHeight <= containerHeight) {
+		cropBox.width = newWidth;
+		cropBox.height = newHeight;
+		// 重新居中
+		cropBox.x = (containerWidth - newWidth) / 2;
+		cropBox.y = (containerHeight - newHeight) / 2;
+	} else {
+		// 如果高度超出,则以高度为准计算
+		cropBox.height = Math.min(newHeight, containerHeight - 40); // 留一20px边距
+		cropBox.width = cropBox.height * currentRatio;
+		cropBox.x = (containerWidth - cropBox.width) / 2;
+		cropBox.y = (containerHeight - cropBox.height) / 2;
+	}
+
+	// 裁剪框调整完成后,再次调整图片尺寸
+	adjustImageSizeAfterCropResize(oldWidth, oldHeight, oldImageWidth, oldImageHeight);
+}
+
+// 裁剪框调整后的图片尺寸调整
+function adjustImageSizeAfterCropResize(
+	oldWidth: number,
+	oldHeight: number,
+	oldImageWidth: number,
+	oldImageHeight: number
+) {
+	if (!imageInfo.loaded) return;
+
+	// 计算裁剪框尺寸变化比例
+	const widthRatio = cropBox.width / oldWidth;
+	const heightRatio = cropBox.height / oldHeight;
+	const avgRatio = (widthRatio + heightRatio) / 2;
+
+	// 根据裁剪框尺寸变化调整图片尺寸
+	const adjustedWidth = oldImageWidth * avgRatio;
+	const adjustedHeight = oldImageHeight * avgRatio;
+
+	// 计算最终尺寸(强制确保能覆盖新的裁剪框)
+	const minSize = calculateMinSize();
+	const containerWidth = rect.width;
+	const containerHeight = rect.height;
+	const maxWidth = containerWidth * props.maxScale;
+	const maxHeight = containerHeight * props.maxScale;
+
+	// 强制应用新的尺寸,不允许小于最小值或大于最大值
+	imageDisplay.width = Math.max(minSize.width, Math.min(maxWidth, adjustedWidth));
+	imageDisplay.height = Math.max(minSize.height, Math.min(maxHeight, adjustedHeight));
+
+	// 图片重新居中
+	imageTransform.translateX = 0;
+	imageTransform.translateY = 0;
+
+	// 检查并调整图片边界
+	adjustImageBounds();
+}
+
+// 检查并调整图片边界,确保图片完全覆盖裁剪框
+function adjustImageBounds() {
+	if (!imageInfo.loaded) return;
+
+	const containerWidth = rect.width;
+	const containerHeight = rect.height;
+
+	// 计算图片中心点在容器中的位置
+	const imageCenterX = containerWidth / 2 + imageTransform.translateX;
+	const imageCenterY = containerHeight / 2 + imageTransform.translateY;
+
+	// 计算图片的边界
+	const imageLeft = imageCenterX - imageDisplay.width / 2;
+	const imageRight = imageCenterX + imageDisplay.width / 2;
+	const imageTop = imageCenterY - imageDisplay.height / 2;
+	const imageBottom = imageCenterY + imageDisplay.height / 2;
+
+	// 裁剪框的边界
+	const cropLeft = cropBox.x;
+	const cropRight = cropBox.x + cropBox.width;
+	const cropTop = cropBox.y;
+	const cropBottom = cropBox.y + cropBox.height;
+
+	// 检查图片是否完全覆盖裁剪框,如果不覆盖则调整位置
+	let newTranslateX = imageTransform.translateX;
+	let newTranslateY = imageTransform.translateY;
+
+	// 检查水平方向
+	if (imageLeft > cropLeft) {
+		// 图片左边界在裁剪框左边界右侧,需要向左移动
+		newTranslateX = imageTransform.translateX - (imageLeft - cropLeft);
+	} else if (imageRight < cropRight) {
+		// 图片右边界在裁剪框右边界左侧,需要向右移动
+		newTranslateX = imageTransform.translateX + (cropRight - imageRight);
+	}
+
+	// 检查垂直方向
+	if (imageTop > cropTop) {
+		// 图片上边界在裁剪框上边界下方,需要向上移动
+		newTranslateY = imageTransform.translateY - (imageTop - cropTop);
+	} else if (imageBottom < cropBottom) {
+		// 图片下边界在裁剪框下边界上方,需要向下移动
+		newTranslateY = imageTransform.translateY + (cropBottom - imageBottom);
+	}
+
+	// 应用调整后的位置
+	imageTransform.translateX = newTranslateX;
+	imageTransform.translateY = newTranslateY;
+}
+
 // 初始化
 onMounted(() => {
-	if (props.src != "") {
-		initCropBox();
-	}
+	initCropBox();
 });
 </script>
 
 <style lang="scss" scoped>
 .cl-cropper {
-	@apply relative overflow-hidden bg-surface-100 rounded-xl;
+	@apply bg-black absolute left-0 top-0 w-full h-full;
+	z-index: 100;
 
 	&.is-disabled {
 		@apply opacity-50;
@@ -620,7 +846,8 @@ onMounted(() => {
 	}
 
 	&__image-container {
-		@apply absolute top-0 left-0 flex items-center justify-center;
+		@apply absolute top-0 left-0 flex items-center justify-center w-full h-full;
+		z-index: 1;
 	}
 
 	&__image {
@@ -629,6 +856,17 @@ onMounted(() => {
 
 	&__crop-box {
 		@apply absolute;
+		z-index: 2;
+		pointer-events: none; // 让裁剪框本身不阻挡触摸事件
+	}
+
+	&__crop-area {
+		@apply relative w-full h-full border border-white border-solid transition-all duration-300;
+		pointer-events: auto; // 恢复裁剪区域的触摸事件
+
+		&.is-resizing {
+			@apply border-primary-500;
+		}
 	}
 
 	&__crop-mask {
@@ -636,30 +874,18 @@ onMounted(() => {
 
 		&--top {
 			@apply top-0 left-0 w-full;
-			height: var(--crop-top);
 		}
 
 		&--right {
 			@apply top-0 right-0 h-full;
-			width: var(--crop-right);
 		}
 
 		&--bottom {
 			@apply bottom-0 left-0 w-full;
-			height: var(--crop-bottom);
 		}
 
 		&--left {
 			@apply top-0 left-0 h-full;
-			width: var(--crop-left);
-		}
-	}
-
-	&__crop-area {
-		@apply relative w-full h-full border-2 border-white border-solid transition-all duration-300;
-
-		&.is-resizing {
-			@apply border-primary-500;
 		}
 	}
 
@@ -671,57 +897,73 @@ onMounted(() => {
 		@apply absolute bg-white opacity-70;
 
 		&--h1 {
-			@apply top-1/3 left-0 w-full h-px;
+			@apply top-1/3 left-0 w-full;
+			height: 0.5px;
 		}
 
 		&--h2 {
-			@apply top-2/3 left-0 w-full h-px;
+			@apply top-2/3 left-0 w-full;
+			height: 0.5px;
 		}
 
 		&--v1 {
-			@apply left-1/3 top-0 w-px h-full;
+			@apply left-1/3 top-0 h-full;
+			width: 0.5px;
 		}
 
 		&--v2 {
-			@apply left-2/3 top-0 w-px h-full;
+			@apply left-2/3 top-0 h-full;
+			width: 0.5px;
 		}
 	}
 
 	&__drag-point {
-		@apply absolute w-3 h-3 bg-white border border-surface-300 border-solid cursor-move transition-all duration-200;
-
-		&:hover {
-			@apply scale-125 border-primary-500;
-		}
+		@apply absolute transition-all duration-200;
+		touch-action: none;
+		// 触发区域为40px
+		width: 40px;
+		height: 40px;
+		// 使用粗线样式代替伪元素
+		border: 3px solid white;
+		background: transparent;
 
 		&--tl {
-			@apply -top-1 -left-1;
-			cursor: nw-resize;
+			top: -20px;
+			left: -20px;
+			border-right: none;
+			border-bottom: none;
 		}
 
 		&--tr {
-			@apply -top-1 -right-1;
-			cursor: ne-resize;
+			top: -20px;
+			right: -20px;
+			border-left: none;
+			border-bottom: none;
 		}
 
 		&--bl {
-			@apply -bottom-1 -left-1;
-			cursor: sw-resize;
+			bottom: -20px;
+			left: -20px;
+			border-right: none;
+			border-top: none;
 		}
 
 		&--br {
-			@apply -bottom-1 -right-1;
-			cursor: se-resize;
+			bottom: -20px;
+			right: -20px;
+			border-left: none;
+			border-top: none;
 		}
-		
+
 		// 缩放时高亮显示
 		.is-resizing & {
-			@apply scale-125 border-primary-500 shadow-md;
+			@apply scale-110;
+			border-color: #3b82f6;
 		}
 	}
 
 	&__buttons {
-		@apply absolute bottom-4 left-0 right-0 flex justify-center space-x-4;
+		@apply absolute bottom-4 left-0 right-0 flex flex-row justify-center;
 	}
 }
 </style>