产品:后台看到用户上传的头像竟然还有长图,小程序端用户上传头像需要裁剪一下为方的
我 :小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-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组件中的保存事件即可