这个裁剪方法可以裁剪圆形、矩形,可以二开,放心食用
先看效果图:
矩形的:
圆形的:
具体的方法:
组件的:
// cropper--index.vue
<template>
<view>
<canvas class="fyj_canvas" canvas-id="myCanvas" :style="{width:'100%', height:canvasHeight+'px',}"/>
<movable-area class="fyj_movable_area text-center hidden" :style="{width:'100%', height:canvasHeight+'px',}">
<movable-view v-if="src" :style="{width:cutWidth +'px', height:cutHeight + 'px'}" class="fyj_movable_view" :x="x"
:y="y" direction="all" :scale="true" @change="movableChange" @scale="handleScale"></movable-view>
<image class="fyj_photo" id="fyj_photo" :src="src" mode="widthFix"/>
</movable-area>
<!--</canvas>-->
<view style="margin-top:20rpx; padding:0 20rpx;">
<button class="pull-left" type="warn" size="mini" @click="getPhoto">选择照片/拍照</button>
<button class="pull-right" type="primary" size="mini" @click="cut">裁剪</button>
<view class="clearfix"></view>
</view>
</view>
</template>
<script setup>
import {ref, reactive, watch, nextTick, getCurrentInstance} from 'vue'
const {aspectRatio} = defineProps({
// 这里定义了innerText属性,属性值可以在组件使用时指定
//宽高比 TODO 这个要求裁剪框是矩形的,如果裁剪框是圆形的,这个属性宽高比一定得是1
aspectRatio: {
type: Number,
default: 5 / 7,
},
})
const screenWidth = ref(uni.getSystemInfoSync().windowWidth)
const canvasHeight = ref(300)
const x = ref(0)
const y = ref(0)
const src = ref('')
const cut_src = ref('')
const cutWidth = ref(0)
const cutHeight = ref(0)
const tempImage = ref('')
const {proxy} = getCurrentInstance()
const emits = defineEmits(['getTempFilePath'])
// 这里是一个自定义方法
//选择照片
const getPhoto = () => {
const ctx = uni.createCanvasContext('myCanvas', proxy)
let obj = uni.createSelectorQuery();
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success(res) {
//清空之前的剪切图
emits('getTempFilePath', {cut_src: '', cutWidth: cutWidth.value, cutHeight: cutHeight.value})
// tempFilePath可以作为img标签的src属性显示图片
const tempFilePaths = res.tempFilePaths[0];
src.value = tempFilePaths
// 这个变量是为了下面圆形裁剪框使用的 可以结合一个配置项判断是否要使用
tempImage.value = tempFilePaths
cut_src.value = ''
setTimeout(function () {
// TODO 这里也可以换成小程序获取图片信息的那个api方法
uni.createSelectorQuery().in(proxy).select('#fyj_photo').boundingClientRect(function (rect) {
console.log(rect);
console.log('图片的信息', rect.height);
canvasHeight.value = rect.height
setCut();
// TODO 矩形裁剪框使用的是这个
// ctx.drawImage(tempFilePaths, 0, 0, screenWidth.value, canvasHeight.value)
// ctx.draw()
// 放到上面去了,防止一开始拿不到
// setCut();
//确保不同大小的图片,切图不会变形
x.value = 0
y.value = 0
}).exec()
}, 100)
}
})
}
//获取图片高度 暂时没用到
// const getHeight = () => {
// const query = uni.createSelectorQuery().in(proxy)
// query.selectAll('#fyj_photo').boundingClientRect()
// query.exec(function (rect) {
// console.log(rect);
// console.log(rect[0].height);
// canvasHeight.value = rect[0].height
// // TODO ...
// ctx.drawImage(tempFilePaths[0], 0, 0, screenWidth.value, canvasHeight.value)
// ctx.draw();
// setCut();
// })
// }
//裁剪框移动事件
const movableChange = debounce((e) => {
console.log('裁剪框移动', e);
x.value = e.detail.x
y.value = e.detail.y
// TODO 这部分可以抽成一个方法配合一个配置项 圆形裁剪框
const ctx = uni.createCanvasContext('myCanvas', proxy)
setTimeout(function () {
// TODO 这里也可以换成小程序获取图片信息的那个api方法
uni.createSelectorQuery().in(proxy).select('#fyj_photo').boundingClientRect(function (rect) {
console.log(rect);
console.log('图片的信息', rect.height);
canvasHeight.value = rect.height
setCut();
// TODO 圆形裁剪框 裁剪圆形的图片
// 开始一个新的路径
ctx.save()
ctx.beginPath();
// 创建一个圆形路径
ctx.arc((x.value + (cutWidth.value / 2)), (y.value + (cutWidth.value / 2)), cutWidth.value / 2, 0, Math.PI * 2, false);
// 设置当前路径为剪切路径
ctx.clip();
// 再次绘制图片,只有在剪切路径内的部分会被绘制
ctx.drawImage(tempImage.value, 0, 0, screenWidth.value, canvasHeight.value);
ctx.restore();
ctx.draw();
// 这个放到上面去了,防止一开始拿不到
// setCut();
// TODO 圆形裁剪框时是不需要的 确保不同大小的图片,切图不会变形
// x.value = 0
// y.value = 0
// TODO 防止第二次圆形绘制失效
ctx.clearRect(0, 0, screenWidth.value, canvasHeight.value)
}).exec()
}, 100)
}, 500)
//截图
const cut = () => {
console.log(cutHeight.value);
uni.canvasToTempFilePath({
// TODO 不管是绘制圆形还是矩形,这里的配置都是一样的
x: x.value,
y: y.value,
width: cutWidth.value,
height: cutHeight.value,
destWidth: cutWidth.value,
destHeight: cutHeight.value,
canvasId: 'myCanvas',
success(res) {
console.log(res.tempFilePath);
cut_src.value = res.tempFilePath
emits('getTempFilePath', {cut_src: cut_src.value, cutWidth: cutWidth.value, cutHeight: cutHeight.value})
}
}, proxy)
}
//动态设置裁剪框大小,确定高度不得超过canvas的高度
const setCut = () => {
// TODO 这里比较重要
cutWidth.value = uni.getSystemInfoSync().windowWidth * 0.8
cutHeight.value = uni.getSystemInfoSync().windowWidth * 0.8 / aspectRatio
if (cutHeight.value - 4 > canvasHeight.value) {
console.log(cutHeight.value);
console.log(canvasHeight.value);
cutHeight.value = canvasHeight.value - 4
cutWidth.value = (canvasHeight.value - 4) * aspectRatio
} else {
cutWidth.value = uni.getSystemInfoSync().windowWidth * 0.8
cutHeight.value = uni.getSystemInfoSync().windowWidth * 0.8 / aspectRatio
}
console.log('裁剪框的宽', cutWidth.value);
console.log('裁剪框的高', cutHeight.value);
}
// 裁剪框缩放事件
const handleScale = debounce((e) => {
console.log('裁剪框缩放', e);
cutWidth.value = cutWidth.value * e.detail.scale
cutHeight.value = cutWidth.value * e.detail.scale
}, 500)
// 防抖
function debounce(func, wait, immediate=false) {
let timeout;
return function executedFunction() {
const context = this;
const args = arguments;
const later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
</script>
<style scoped lang="scss">
// TODO 隐藏画布可以吧注释放开
.fyj_canvas {
//position: absolute;
//left: 0;
//top: -71vh;
//z-index: -1;
//opacity: 0;
}
.fyj_movable_area {
width: 100%;
height: auto;
position: relative;
background: rgba(0, 0, 0, 0.3);
z-index: 99;
}
.fyj_movable_view {
border: 2px dashed red;
// TODO 圆形裁剪框时使用的
border-radius: 50%;
}
.fyj_photo {
width: 100%;
}
.fyj_footer {
margin-top: 20rpx 0;
}
.fyj_footerBtn {
width: 100%;
display: inline-block;
color: #fff;
border-radius: 0;
font-size: 32rpx;
}
.fyj_sure {
background: #fc6b47;
}
.pull-left {
float: left;
}
.pull-right {
float: right;
}
.clearfix {
clear: both;
}
.text-center {
text-align: center;
}
</style>
使用组件:
// testCropper--index.vue
<template>
<view>
<!-- aspectRatio 剪裁图片的宽高比 -->
<cropper :aspectRatio="1" @getTempFilePath="getCutsrc"></cropper>
<view v-if="cut_src" class="fyj_cutDiv text-center">
<image :style="{width: cutWidth + 'px', height: cutHeight + 'px'}" class="fyj_cut_photo" :src="cut_src" mode="widthFix"/>
</view>
<view v-if="cut_src" class="fyj_footer text-center">
<button class="fyj_footerBtn fyj_sure" @click='sure'>确定</button>
</view>
</view>
</template>
<script setup>
import {ref} from 'vue'
import cropper from '../components/cropper/index.vue'
const cut_src = ref('');
const cutWidth = ref(0);
const cutHeight = ref(0);
const getCutsrc = (e) => {
console.log('子组件的传值', e)
cut_src.value = e.cut_src;
cutWidth.value = e.cutWidth;
cutHeight.value = e.cutHeight;
}
</script>
<style scoped lang="scss">
.fyj_footer {
margin-top: 20rpx 0;
}
.fyj_footerBtn {
width: 100%;
display: inline-block;
color: #fff;
border-radius: 0;
font-size: 32rpx;
}
.fyj_sure {
background: #fc6b47;
}
.fyj_cutDiv {
margin: 20rpx 0;
}
</style>
参考:
https://www.jb51.net/article/249152.htm
https://blog.csdn.net/hanjiepo/article/details/132428196
标签:cutHeight,const,裁剪,value,圆形,console,矩形,cutWidth From: https://blog.csdn.net/qq_42238378/article/details/136975154