uni-app 图片裁剪上传 vue3 vue2 均兼容 钉钉 微信

本文介绍了在uni-app和vue3环境下,由于找不到合适的图片裁剪插件,作者决定自行动手开发一个简单的图片裁剪组件。通过使用movable-area和movable-view实现拖拽和缩放功能,结合canvas进行图片截取。组件包含显示、隐藏、初始化图片、计算偏移量和缩放比例等方法,并提供了在不同小程序平台(如微信、钉钉)下的实现方式。
摘要由CSDN通过智能技术生成

背景

产品:后台看到用户上传的头像竟然还有长图,小程序端用户上传头像需要裁剪一下为方的
我 :小case!不就是找个插件用一下嘛
因为我们用的是uni-app+vue3 兼容钉钉 微信 我苦苦找寻了半天插件市场并没有合适的
这这么办…看市面上的插件写的都好复杂有的还使用了wxs 对这些插件魔改根本无从下手啊
牛皮已经吹出去了 要不还是自己写一个把. 好像可以用movable-area 实现拖拽 后面计算的时时候可以直接用回调的参数. 嗯~ 这好像是最简单的方案了
直接开干

组件代码

src/components下面创建lvClip.vue文件 内容如下

<template>
	<view class="picture-tailor" :class="{ 'picture-tailor-show': isShow }">
		<movable-area class="picture-area">
			<movable-view
				class="picture-view"
				:style="{ width: img_width / img_scaling + 'px', height: img_height / img_scaling + 'px' }"
				direction="all"
				:x="offsetX"
				:y="offsetY"
				scale="true"
				:scale-min="scaleMin"
				@change="movableChange"
				@scale="movableScale">
				<image :style="{ width: img_width / img_scaling + 'px', height: img_height / img_scaling + 'px' }" :src="pictureSrc"></image>
			</movable-view>
		</movable-area>
		<view class="select-box"></view>
		<button class="button-ok" @click="createImg">完成</button>
		<button class="button-no" @click="hide">取消</button>
		<canvas type="2d" id="picture-canvas" class="canvas-view"></canvas>
	</view>
</template>

<script>
	// rpx转px
	function rpxToPx(rpx) {
		const screenWidth = uni.getSystemInfoSync().screenWidth
		return (screenWidth * Number.parseInt(rpx)) / 750
	}
	
	// 480rpx 转px
	let tailorSize = rpxToPx(480); // 需要截取的尺寸480rpx x 480rpx,此变量要和样式中的480rpx和240rpx相对应,240rpx为此变量的一半,若要修改成其他值一定要一一对应
	let newOffsetX = 0; // 拖动缩放完成后的X轴偏移量
	let newOffsetY = 0; // 拖动缩放完成后的Y轴偏移量

	export default {
		name: "lv-clip",
		data() {
			return {
				pictureSrc:'',// 图片
				offsetX: 0, // 图像初始化的X轴偏移量
				offsetY: 0, // 图像初始化的Y轴偏移量
				img_width: 0, // 图片真实宽度
				img_height: 0, // 图片真实高度
				img_scaling: 1, //图片初始化缩放比例
				scale: 1, // 拖动缩放完成后的图片缩放比例
				scaleMin: 0.5, // 最小缩放值
				isShow: false
			};
		},
		methods: {
			// 显示组件
			show(img) {
				this.pictureSrc = img; // 赋值图片
				this.getImgInfo(); // 初始化图片
				this.isShow = true; // 显示组件
			},
			// 隐藏组件
			hide() {
				this.isShow = false;
			},
			// 初始化图片
			getImgInfo() {
				uni.getImageInfo({
					src: this.pictureSrc,
					success: res => {
						// 图片宽高
						this.img_width = res.width;
						this.img_height = res.height;

						// 把最小的边撑满
						let count = this.img_width <= this.img_height ? this.img_width : this.img_height;
						this.img_scaling = count / tailorSize;
						this.scaleMin = 1;

						// 计算图片居中显示时的偏移量
						this.offsetX = -(this.img_width / this.img_scaling / 2 - tailorSize / 2);
						this.offsetY = -(this.img_height / this.img_scaling / 2 - tailorSize / 2);

						// 获取新的偏移量
						newOffsetX = this.offsetX;
						newOffsetY = this.offsetY;
					}
				});
			},
			// 计算拖动偏移量
			movableChange(e) {
				newOffsetX = e.detail.x;
				newOffsetY = e.detail.y;
			},
			// 计算缩放比例和偏移量
			movableScale(e) {
				newOffsetX = e.detail.x;
				newOffsetY = e.detail.y;
				this.scale = e.detail.scale;
			},
			// 截取图片
			createImg() {
				
				// #ifdef MP-WEIXIN
				uni.createSelectorQuery().in(this).select('#picture-canvas').fields({ node: true, size: true })
					.exec((res) => {
						const canvas = res[0].node;
						canvas.width = tailorSize;
						canvas.height = tailorSize;
						
						const ctx = canvas.getContext('2d')
						
						let headerImg = canvas.createImage();	//创建iamge实例
						headerImg.src = this.pictureSrc;	//临时图片路径
						headerImg.onload = () => {
							ctx.drawImage(
								headerImg,
								newOffsetX,//起点 X 坐标
								newOffsetY,//起点 Y 坐标
								(this.img_width / this.img_scaling) * this.scale,//终点 X 坐标
								(this.img_height / this.img_scaling) * this.scale//终点 Y 坐标
							);
							ctx.restore();	//保存上下文	
							uni.canvasToTempFilePath({
								quality: 1,// 图片质量0-1
								canvas:canvas,
								fileType: 'png',
								success: res => {
									// 在H5平台下,tempFilePath 为 base64
									this.hide(); // 关闭
									this.$emit("createImg", res.tempFilePath);
								},
								fail: function (res) {
									this.hide(); // 关闭
								}
							},this);
						};
					})
				// #endif
				
				
				// #ifdef MP-DINGTALK
				let ctx = uni.createCanvasContext("picture-canvas", this);
				ctx.drawImage(
					this.pictureSrc,
					newOffsetX,//起点 X 坐标
					newOffsetY,//起点 Y 坐标
					(this.img_width / this.img_scaling) * this.scale,//终点 X 坐标
					(this.img_height / this.img_scaling) * this.scale//终点 Y 坐标
				);
				ctx.draw(false, () => {
					uni.canvasToTempFilePath(
						{
							quality: 1,// 图片质量0-1
							canvasId: "picture-canvas",// 画布标识,传入 <canvas/> 的 canvas-id(支付宝小程序是id、其他平台是canvas-id)
							success: res => {
								this.hide(); // 关闭
								this.$emit("createImg", res.tempFilePath);
							},
							fail: function (res) {
								this.hide(); // 关闭
							}
						},
						this
					);
				});
				// #endif
				
				
			}
		}
	};
</script>

<style scoped>
	.picture-tailor {
		position: fixed;
		top: 0;
		left: 0;
		bottom: 0;
		right: 0;
		width: 100%;
		height: 100%;
		background-color: #000000;
		transform: translateX(100%);
		transition: all 200ms ease;
		overflow: hidden;
		visibility: hidden;
	}

	.picture-tailor-show {
		transform: translateY(0) !important;
		visibility: visible;
	}

	/* 拖动域的位置和大小 */
	.picture-tailor .picture-area {
		width: 480rpx;
		height: 480rpx;
		position: absolute;
		/* 使其居中定位 */
		top: calc(50% - 240rpx);
		left: calc(50% - 240rpx);
	}

	/* 拖动控件的大小 */
	.picture-area .picture-view {
		min-width: 480rpx;
		min-height: 480rpx;
	}

	/* 中间选择框的大小,本意是视觉上模拟拖动域 */
	.select-box {
		position: absolute;
		top: calc(50% - 240rpx);
		left: calc(50% - 240rpx);
		width: 480rpx;
		height: 480rpx;
		box-sizing: border-box;
		border-radius: 50%;
		border: #ffffff 1rpx solid;
		pointer-events: none;
	}

	.button-ok {
		position: absolute;
		bottom: 40rpx;
		right: 40rpx;
		width: 120rpx;
		background-color: #007aff;
		font-size: 28rpx;
		color: #ffffff;
	}
	.button-no {
		position: absolute;
		bottom: 40rpx;
		left: 40rpx;
		width: 120rpx;
		background-color: #007aff;
		font-size: 28rpx;
		color: #ffffff;
	}

	/* 画布大小,画布大小就是截取的原始大小 */
	.canvas-view {
		width: 480rpx;
		height: 480rpx;
		position: relative;
		left: -9999rpx;
		visibility: hidden;
	}
</style>

使用方法

下面是vue3的写法 vue2自行改造一下即可

<template>
	<view class="page">
		<button type="primary" @click="choosePhoto">选择图片</button>
		<image class="iii" :src="tailorPath" mode="widthFix" ></image>
		<lv-clip ref="lvClipDom" @createImg="createImg"></lv-clip>
	</view>
</template>

<script setup>
	import lvClip from "@/components/lvClip.vue";
	import { ref } from "vue";
	
	const lvClipDom = ref(null);
	const tailorPath = ref('')
	
	const choosePhoto = ()=>{
		uni.chooseImage({
			count: 1,
			sizeType: ["compressed"],
			success: res => {
				if(res.tempFilePaths[0]){
				// 传入临时路径
					lvClipDom.value.show(res.tempFilePaths[0]);
				}
			}
		});
	}
	// 拿到临时路径
	const createImg = (e) => {
		console.log(e)
		tailorPath.value = e;
	}


</script>
<style scoped>
	
	.iii{
		width: 600rpx;
		border: 2rpx solid red;
	}

</style>

小结

这样就实现了一个简单功能的图片裁剪
兼容其他端的时候只需要改一下lvClip.vue组件中的保存事件即可

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值