从零打造 UniApp/小程序图片裁切组件:原理与实现全解析
在 UniApp 或小程序开发中,图片裁切是非常常见的功能需求,比如用户头像裁剪、证件照裁剪等场景。本文将详细拆解一个可复用、高定制化的图片裁切组件,从组件结构、核心逻辑到样式实现,带你完整掌握图片裁切组件的开发思路和实现细节。

一、组件整体设计思路
本组件基于 UniApp 跨端特性开发,兼容小程序和 H5 等平台,核心实现了以下功能:
- 支持图片的缩放、平移、旋转操作
- 可自定义裁切框的大小、颜色、比例锁定
- 限制图片移动范围,避免裁切空白区域
- 多端兼容的图片裁切结果导出(支持 base64 和临时文件路径)
- 自定义操作栏或使用默认操作栏
组件整体分为三个核心部分:
- 模板层(view) :负责组件的 UI 结构,包括裁切框、图片展示、操作栏等
- 逻辑层(js) :处理触摸事件、图片变换、裁切计算、画布绘制等核心逻辑
- 样式层(css) :控制组件的视觉表现,保证裁切框、图片、操作栏的布局和交互体验
二、模板层(View)详解
模板层是组件的 UI 载体,主要负责渲染裁切区域、图片、裁切框和操作栏,同时绑定各类触摸事件。
2.1 核心结构拆解
<template>
<view class="tui-container" id="tui-container" @touchmove.stop.prevent="stop">
<!-- 裁切主区域:包含遮罩层和裁切框 -->
<view class="tui-image-cropper" @touchend="cutTouchEnd" @touchstart="cutTouchStart" @touchmove="cutTouchMove">
<!-- 遮罩层:用于显示裁切框外的半透明区域 -->
<view class="tui-content">
<view
class="tui-content-top tui-bg-transparent"
:style="{
height: cutY + 'px',
transitionProperty: cutAnimation ? '' : 'background',
}"
></view>
<view class="tui-content-middle" :style="{ height: canvasHeight + 'px' }">
<view class="tui-bg-transparent" :style="{ width: cutX + 'px' }"></view>
<!-- 裁切框主体 -->
<view
class="tui-cropper-box"
:style="{
width: canvasWidth + 'px',
height: canvasHeight + 'px',
borderColor: borderColor,
}"
>
<!-- 裁切框四个角的控制点 -->
<view
v-for="(item, index) in 4"
:key="index"
class="tui-edge"
:class="[`tui-${index < 2 ? 'top' : 'bottom'}-${index === 0 || index === 2 ? 'left' : 'right'}`]"
:style="{
width: edgeWidth,
height: edgeWidth,
borderColor: edgeColor,
borderWidth: edgeBorderWidth,
left: index === 0 || index === 2 ? `-${edgeOffsets}` : 'auto',
right: index === 1 || index === 3 ? `-${edgeOffsets}` : 'auto',
top: index < 2 ? `-${edgeOffsets}` : 'auto',
bottom: index > 1 ? `-${edgeOffsets}` : 'auto',
}"
></view>
</view>
<view class="tui-flex-auto tui-bg-transparent"></view>
</view>
<view class="tui-flex-auto tui-bg-transparent"></view>
</view>
<!-- 待裁切的图片:绑定触摸事件实现缩放平移 -->
<image
@load="imageLoad"
@error="imageLoad"
@touchstart="start"
@touchmove="move"
@touchend="end"
:style="{
width: imgWidth ? imgWidth + 'px' : 'auto',
height: imgHeight ? imgHeight + 'px' : 'auto',
transform: imgTransform,
transitionDuration: (cutAnimation ? 0.35 : 0) + 's',
}"
class="tui-cropper-image"
:src="imageUrl"
v-if="imageUrl"
mode="widthFix"
></image>
</view>
<!-- 隐藏的画布:用于最终裁切图片的绘制和导出 -->
<canvas
canvas-id="tui-image-cropper"
id="tui-image-cropper"
:disable-scroll="true"
:style="{
width: CROPPER_WIDTH * scaleRatio + 'px',
height: CROPPER_HEIGHT * scaleRatio + 'px',
}"
class="tui-cropper-canvas"
></canvas>
<!-- 默认操作栏:取消、旋转、完成 -->
<view class="tui-cropper-tabbar" v-if="!custom">
<view class="tui-op-btn" @tap.stop="back">取消</view>
<image :src="rotateImg" class="tui-rotate-img" @tap="setAngle"></image>
<view class="tui-op-btn" @tap.stop="getImage">完成</view>
</view>
</view>
</template>2.2 关键元素说明
- tui-container :组件最外层容器,占满视口,阻止默认的触摸滚动行为
- tui-content :遮罩层结构,通过
cutX和cutY控制裁切框的位置,其余区域显示半透明黑色遮罩 - tui-edge :裁切框的四个角控制点,支持拖动调整裁切框大小
- tui-cropper-image :待裁切的图片,通过
transform实现缩放、平移、旋转效果 - canvas :隐藏的画布元素,用于最终将裁切区域的图片绘制并导出为临时文件或 base64
- tui-cropper-tabbar :默认操作栏,提供取消、旋转、完成三个功能按钮
2.3 样式引入与脚本挂载
<script>
// 引入核心逻辑文件
import control from './tui-image-cropper.js';
export default control;
</script>
<style scoped>
// 引入组件样式
@import url('tui-image-cropper.css');
</style>模板层通过引入外部的 js 逻辑和 css 样式,实现了结构与逻辑、样式的分离,便于维护和复用。
三、逻辑层(JS)详解
逻辑层是图片裁切组件的核心,负责处理所有交互逻辑和裁切计算,下面分模块拆解核心逻辑。
3.1 组件配置项(Props)
首先定义了组件的可配置属性,让组件具备高度的定制化能力:
props: {
// 图片路径
imageUrl: { type: String, default: '' },
// 裁切框高度(默认正方形)
height: { type: Number, default: 280 },
// 裁切框宽度
width: { type: Number, default: 280 },
// 裁切框最小宽高限制
minWidth: { type: Number, default: 100 },
minHeight: { type: Number, default: 100 },
// 裁切框最大宽高限制
maxWidth: { type: Number, default: 360 },
maxHeight: { type: Number, default: 360 },
// 裁切框样式配置
borderColor: { type: String, default: 'rgba(255,255,255,0.1)' },
edgeColor: { type: String, default: '#FFFFFF' },
edgeWidth: { type: String, default: '34rpx' },
edgeBorderWidth: { type: String, default: '6rpx' },
edgeOffsets: { type: String, default: '6rpx' },
// 裁切框比例锁定配置
lockWidth: { type: Boolean, default: false },
lockHeight: { type: Boolean, default: false },
lockRatio: { type: Boolean, default: false },
// 图片导出配置
scaleRatio: { type: Number, default: 2 },
quality: { type: Number, default: 0.8 },
// 图片变换配置
rotateAngle: { type: Number, default: 0 },
minScale: { type: Number, default: 0.5 },
maxScale: { type: Number, default: 2 },
disableRotate: { type: Boolean, default: true },
limitMove: { type: Boolean, default: true },
// 自定义配置
custom: { type: Boolean, default: false },
startCutting: { type: [Number, Boolean], default: 0 },
isBase64: { type: Boolean, default: false },
loadding: { type: Boolean, default: true },
rotateImg: { type: String, default: 'https://i.loli.net/2021/03/06/e67aKoVM4Hkn8tf.png' },
},这些配置项覆盖了裁切框样式、图片变换限制、导出格式等核心需求,开发者可根据实际场景灵活配置。
3.2 响应式数据(Data)
定义组件内部的响应式数据,用于跟踪图片状态、裁切框状态、触摸状态等:
data() {
return {
MOVE_THROTTLE: null, // 触摸移动节流定时器
MOVE_THROTTLE_FLAG: true, // 节流标识
TIME_CUT_CENTER: null, // 裁切框居中定时器
CROPPER_WIDTH: 200, // 裁切框宽
CROPPER_HEIGHT: 200, // 裁切框高
CUT_START: null, // 裁切框触摸起始信息
cutX: 0, // 裁切框 X 轴起点
cutY: 0, // 裁切框 Y 轴起点
touchRelative: [{ x: 0, y: 0 }], // 手指与图片中心的相对位置
flagCutTouch: false, // 是否拖动裁切框
hypotenuseLength: 0, // 双指触摸斜边长度(用于缩放计算)
flagEndTouch: false, // 是否结束触摸
canvasWidth: 0, // 实际裁切框宽度
canvasHeight: 0, // 实际裁切框高度
imgWidth: 0, // 图片宽度
imgHeight: 0, // 图片高度
scale: 1, // 图片缩放比
angle: 0, // 图片旋转角度
cutAnimation: false, // 是否开启过渡动画
cutAnimationTime: null, // 动画定时器
imgTop: 0, // 图片上边距
imgLeft: 0, // 图片左边距
ctx: null, // 画布上下文
boxSize: null, // 容器尺寸
};
},3.3 计算属性(Computed)
核心计算属性 imgTransform 用于生成图片的变换样式,整合了平移、缩放、旋转效果:
computed: {
imgTransform: function () {
return `translate3d(${this.imgLeft - this.imgWidth / 2}px,${this.imgTop - this.imgHeight / 2}px,0) scale(${
this.scale
}) rotate(${this.angle}deg)`;
},
},通过 CSS3 的 translate3d 、 scale 、 rotate 实现高性能的图片变换,利用硬件加速提升体验。
3.4 监听属性(Watch)
监听关键数据的变化,实现响应式逻辑处理:
watch: {
// 监听图片 URL 变化,重置并加载图片
imageUrl: {
handler(val, oldVal) {
this.imageReset();
this.showLoading();
uni.getImageInfo({
src: val,
success: res => {
this.imgComputeSize(res.width, res.height); // 计算图片尺寸
if (this.limitMove) {
this.imgMarginDetectionScale(); // 限制图片移动范围
}
},
fail: err => {
this.imgComputeSize();
if (this.limitMove) {
this.imgMarginDetectionScale();
}
},
});
},
immediate: true,
},
// 监听裁切框宽高变化,限制最小值并重新计算位置
canvasWidth(val) {
if (val < this.minWidth) {
this.canvasWidth = this.minWidth;
}
this.computeCutSize();
},
canvasHeight(val) {
if (val < this.minHeight) {
this.canvasHeight = this.minHeight;
}
this.computeCutSize();
},
// 其他监听逻辑...
},监听属性确保了图片加载、裁切框调整、旋转角度变化等操作能触发对应的逻辑处理,保证组件状态的一致性。
3.5 核心方法解析
3.5.1 图片尺寸计算(imgComputeSize)
imgComputeSize(width, height) {
// 默认按图片最小边 = 对应裁切框尺寸
let imgWidth = width,
imgHeight = height;
if (imgWidth && imgHeight) {
// 按裁切框比例缩放图片,保证图片适配裁切框
if (imgWidth / imgHeight > (this.canvasWidth || this.width) / (this.canvasHeight || this.height)) {
imgHeight = this.canvasHeight || this.height;
imgWidth = (width / height) * imgHeight;
} else {
imgWidth = this.canvasWidth || this.width;
imgHeight = (height / width) * imgWidth;
}
} else {
let sys = this.boxSize || (await this.cbxs());
imgWidth = sys.width;
imgHeight = 0;
}
this.imgWidth = imgWidth;
this.imgHeight = imgHeight;
},该方法根据图片原始尺寸和裁切框比例,计算出图片的初始显示尺寸,保证图片能适配裁切框。
3.5.2 触摸事件处理(start/move/end)
处理图片的单指平移、双指缩放和旋转:
// 触摸开始
start(e) {
this.flagEndTouch = false;
if (e.touches.length == 1) {
// 单指拖动:记录手指与图片中心的相对位置
this.touchRelative[0] = {
x: e.touches[0].clientX - this.imgLeft,
y: e.touches[0].clientY - this.imgTop,
};
} else {
// 双指操作:计算初始斜边长度
let width = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
let height = Math.abs(e.touches[0].clientY - e.touches[1].clientY);
this.touchRelative = [
{ x: e.touches[0].clientX - this.imgLeft, y: e.touches[0].clientY - this.imgTop },
{ x: e.touches[1].clientX - this.imgLeft, y: e.touches[1].clientY - this.imgTop },
];
this.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
}
},
// 触摸移动
move(e) {
if (this.flagEndTouch || !this.MOVE_THROTTLE_FLAG) return;
this.MOVE_THROTTLE_FLAG = false;
this.moveThrottle(); // 节流处理
this.moveDuring();
if (e.touches.length == 1) {
// 单指平移:更新图片位置并检测边缘
let left = e.touches[0].clientX - this.touchRelative[0].x,
top = e.touches[0].clientY - this.touchRelative[0].y;
this.imgLeft = left;
this.imgTop = top;
this.imgMarginDetectionPosition(); // 边缘检测,避免空白
} else {
// 双指缩放:计算缩放比例并限制范围
let width = Math.abs(e.touches[0].clientX - e.touches[1].clientX),
height = Math.abs(e.touches[0].clientY - e.touches[1].clientY),
hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
scale = this.scale * (hypotenuse / this.hypotenuseLength),
current_deg = 0;
// 限制缩放比例
scale = scale <= this.minScale ? this.minScale : scale;
scale = scale >= this.maxScale ? this.maxScale : scale;
// 边缘检测并更新缩放
this.imgMarginDetectionScale(scale);
// 双指旋转(如果未禁用)
if (!this.disableRotate) {
// 计算旋转角度
let touchRelative = [
{ x: e.touches[0].clientX - this.imgLeft, y: e.touches[0].clientY - this.imgTop },
{ x: e.touches[1].clientX - this.imgLeft, y: e.touches[1].clientY - this.imgTop },
];
let first_atan = (180 / Math.PI) * Math.atan2(touchRelative[0].y, touchRelative[0].x);
let first_atan_old = (180 / Math.PI) * Math.atan2(this.touchRelative[0].y, this.touchRelative[0].x);
let second_atan = (180 / Math.PI) * Math.atan2(touchRelative[1].y, touchRelative[1].x);
let second_atan_old = (180 / Math.PI) * Math.atan2(this.touchRelative[1].y, this.touchRelative[1].x);
let first_deg = first_atan - first_atan_old,
second_deg = second_atan - second_atan_old;
if (first_deg != 0) current_deg = first_deg;
else if (second_deg != 0) current_deg = second_deg;
this.touchRelative = touchRelative;
this.hypotenuseLength = hypotenuse;
this.angle += current_deg;
}
}
},
// 触摸结束
end(e) {
this.flagEndTouch = true;
this.moveStop();
},这组方法实现了图片的核心交互逻辑,通过触摸事件的坐标计算,实现了流畅的平移、缩放、旋转效果,同时加入了节流和边缘检测,保证交互体验和准确性。
3.5.3 图片裁切(getImage)
这是组件的核心方法,负责将裁切区域的图片绘制到画布并导出:
getImage() {
if (!this.imageUrl) {
uni.showToast({ title: '请选择图片', icon: 'none' });
return;
}
this.loadding && this.showLoading();
let draw = async () => {
// 计算图片实际绘制尺寸
let imgWidth = this.imgWidth * this.scale * this.scaleRatio;
let imgHeight = this.imgHeight * this.scale * this.scaleRatio;
// 计算画布与图片的相对位置
let xpos = this.imgLeft - this.cutX;
let ypos = this.imgTop - this.cutY;
// 旋转画布
this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio);
this.ctx.rotate((this.angle * Math.PI) / 180);
// 处理网络图片(转本地路径)
let imgUrl = this.imageUrl;
// #ifdef APP-PLUS || MP-WEIXIN
if (~this.imageUrl.indexOf('https:')) {
imgUrl = await this.getLocalImage(this.imageUrl);
}
// #endif
// 绘制图片到画布
this.ctx.drawImage(imgUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
// 绘制完成后导出图片
this.ctx.draw(false, () => {
let params = {
width: this.canvasWidth * this.scaleRatio,
height: Math.round(this.canvasHeight * this.scaleRatio),
destWidth: this.canvasWidth * this.scaleRatio,
destHeight: Math.round(this.canvasHeight) * this.scaleRatio,
fileType: 'png',
quality: this.quality,
};
let data = {
url: '',
base64: '',
width: this.canvasWidth * this.scaleRatio,
height: this.canvasHeight * this.scaleRatio,
};
// 支付宝小程序适配
// #ifdef MP-ALIPAY
if (this.isBase64) {
this.ctx.toDataURL(params).then(dataURL => {
data.base64 = dataURL;
this.loadding && uni.hideLoading();
this.$emit('cropper', data);
});
} else {
this.ctx.toTempFilePath({
...params,
success: res => {
data.url = res.tempFilePath;
this.loadding && uni.hideLoading();
this.$emit('cropper', data);
},
});
}
// #endif
// 其他平台适配
// #ifndef MP-ALIPAY
if (this.isBase64) {
uni.canvasGetImageData({
canvasId: 'tui-image-cropper',
x: 0,
y: 0,
width: this.canvasWidth * this.scaleRatio,
height: Math.round(this.canvasHeight * this.scaleRatio),
success: res => {
const arrayBuffer = new Uint8Array(res.data);
const base64 = uni.arrayBufferToBase64(arrayBuffer);
data.base64 = base64;
this.loadding && uni.hideLoading();
this.$emit('cropper', data);
},
}, this);
} else {
uni.canvasToTempFilePath({
...params,
canvasId: 'tui-image-cropper',
success: res => {
data.url = res.tempFilePath;
// H5 端默认返回 base64
// #ifdef H5
data.base64 = res.tempFilePath;
// #endif
this.loadding && uni.hideLoading();
this.$emit('cropper', data);
},
}, this);
}
// #endif
});
};
// 确保裁切框尺寸正确后绘制
if (this.CROPPER_WIDTH != this.canvasWidth || this.CROPPER_HEIGHT != this.canvasHeight) {
this.CROPPER_WIDTH = this.canvasWidth;
this.CROPPER_HEIGHT = this.canvasHeight;
this.ctx.draw();
this.$nextTick(() => {
setTimeout(() => {
draw();
}, 100);
});
} else {
draw();
}
},该方法实现了多端兼容的图片裁切导出,核心步骤包括:
- 计算图片的实际绘制尺寸和位置
- 旋转画布以匹配图片的旋转角度
- 处理网络图片转本地路径(小程序限制)
- 将图片绘制到画布
- 根据配置导出 base64 或临时文件路径
- 通过
cropper事件返回裁切结果
3.6 生命周期钩子(Mounted)
组件挂载时初始化关键数据和画布上下文:
async mounted() {
this.boxSize = await this.cbxs(); // 获取容器尺寸
this.imgTop = this.boxSize.width / 2;
this.imgLeft = this.boxSize.height / 2;
this.CROPPER_WIDTH = this.width;
this.CROPPER_HEIGHT = this.height;
this.canvasHeight = this.height;
this.canvasWidth = this.width;
this.ctx = uni.createCanvasContext('tui-image-cropper', this); // 创建画布上下文
this.setCutCenter(); // 设置裁切框居中
this.computeCutSize(); // 计算裁切框尺寸
this.cutDetectionPosition(); // 检测裁切框位置
setTimeout(() => {
this.$emit('ready', {}); // 触发就绪事件
}, 200);
},四、样式层(CSS)详解
样式层负责组件的视觉表现,保证裁切框、图片、操作栏的布局和交互体验:
4.1 核心样式结构
/* 外层容器:占满视口,绝对定位 */
.tui-container {
width: 100vw;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 99;
overflow: hidden;
}
/* 裁切主区域:相对容器定位 */
.tui-image-cropper {
width: 100vw;
height: 100%;
position: absolute;
}
/* 遮罩层:绝对定位,z-index 高于图片,实现裁切框遮罩效果 */
.tui-content {
width: 100vw;
height: 100%;
position: absolute;
z-index: 9;
display: flex;
flex-direction: column;
pointer-events: none; /* 不阻挡图片的触摸事件 */
}
/* 半透明遮罩背景 */
.tui-bg-transparent {
background-color: rgba(0, 0, 0, 0.6);
transition-duration: 0.35s;
}
/* 裁切框样式 */
.tui-cropper-box {
position: relative;
border-style: solid;
border-width: 1rpx;
box-sizing: border-box;
}
/* 图片样式:绝对定位,transform-origin 为中心,实现旋转缩放 */
.tui-cropper-image {
width: 100%;
border-style: none;
position: absolute;
top: 0;
left: 0;
z-index: 2;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-origin: center;
}
/* 隐藏的画布:定位到视口外,不影响 UI */
.tui-cropper-canvas {
position: fixed;
z-index: 10;
left: -2000px;
top: -2000px;
pointer-events: none;
}
/* 裁切框角控制点样式 */
.tui-edge {
border-style: solid;
pointer-events: auto;
position: absolute;
box-sizing: border-box;
}
/* 四个角的边框样式 */
.tui-top-left { border-bottom-width: 0 !important; border-right-width: 0 !important; }
.tui-top-right { border-bottom-width: 0 !important; border-left-width: 0 !important; }
.tui-bottom-left { border-top-width: 0 !important; border-right-width: 0 !important; }
.tui-bottom-right { border-top-width: 0 !important; border-left-width: 0 !important; }
/* 操作栏样式 */
.tui-cropper-tabbar {
width: 100%;
height: 120rpx;
padding: 0 40rpx;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 0;
z-index: 99;
display: flex;
align-items: center;
justify-content: space-between;
color: #ffffff;
font-size: 32rpx;
}4.2 关键样式说明
- pointer-events: none :遮罩层设置该属性,确保不会阻挡图片的触摸事件
- transform-origin: center :图片的变换原点设为中心,保证缩放旋转围绕中心进行
- backface-visibility: hidden :优化图片变换时的性能,避免闪烁
- 绝对定位+z-index :通过层级控制确保裁切框在图片上方,操作栏在最顶层
- 隐藏的 canvas :将 canvas 定位到视口外,既不影响 UI 又能正常绘制
五、组件使用示例
<template>
<view>
<button @tap="chooseImage">选择图片</button>
<tui-image-cropper
v-if="imageUrl"
:imageUrl="imageUrl"
:width="300"
:height="300"
:lockRatio="true"
@cropper="onCropper"
@ready="onReady"
></tui-image-cropper>
<image v-if="croppedImage" :src="croppedImage" mode="widthFix"></image>
</view>
</template>
<script>
import tuiImageCropper from '@/components/tui-image-cropper/tui-image-cropper.vue';
export default {
components: { tuiImageCropper },
data() {
return {
imageUrl: '',
croppedImage: ''
};
},
methods: {
// 选择图片
chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.imageUrl = res.tempFilePaths[0];
}
});
},
// 裁切完成回调
onCropper(data) {
this.croppedImage = data.url;
this.imageUrl = '';
console.log('裁切结果:', data);
},
// 组件就绪回调
onReady() {
console.log('裁切组件已就绪');
}
}
};
</script>六、注意事项与优化建议
6.1 使用注意事项
- 网络图片处理 :小程序中绘制网络图片到画布需要先下载为本地路径,组件已内置该逻辑,但需确保域名已配置白名单
- 样式隔离 :使用
scoped样式时,确保样式能正确作用到组件内部 - 性能优化 :图片缩放旋转加入了节流处理,避免频繁触发导致性能问题
- 多端兼容 :不同平台的 canvas API 略有差异,组件已做适配,但需测试验证
6.2 优化建议
- 添加手势识别 :可引入专门的手势库,提升触摸体验
- 支持自由裁切 :增加自由裁切模式,不限制比例
- 预设裁切比例 :添加常用比例(如 1:1、4:3、16:9)的快捷切换
- 图片压缩 :在导出前增加图片压缩选项,适配不同场景需求
- 错误处理 :完善图片加载失败、画布绘制失败等异常场景的处理
总结
本文详细拆解了一个功能完整的 UniApp/小程序图片裁切组件,核心要点包括:
- 组件采用分层设计,模板层负责 UI 渲染,逻辑层处理核心交互,样式层控制视觉表现,结构清晰易维护
- 核心逻辑基于触摸事件的坐标计算,实现了图片的平移、缩放、旋转,同时加入边缘检测避免裁切空白
- 利用 Canvas 绘制并导出裁切结果,兼容多平台的导出格式(base64/临时文件路径)
- 组件提供丰富的配置项,支持裁切框样式、图片变换限制、操作栏自定义等定制化需求
该组件可直接应用于实际项目中,也可根据具体需求扩展更多功能,是 UniApp/小程序开发中图片裁切场景的理想解决方案。




