参考文章:https://blog.csdn.net/jimojianghu/article/details/129408840
目录
一:.js文件
var util = require("../../utils/util.js");
var app = getApp();
// 系统信息
const sys = wx.getSystemInfoSync()
// 画布上下文
let preCtx = null
// 图片原始路径和大小
let originPath = ''
// 当前展示的图片实际大小
// 裁剪后还可以继续裁剪
let imageWidth = 0
let imageHeight = 0
// 以下为裁剪框缩放时临时变量
// 当前操作的缩放角
let activeCorner = ''
// 裁剪框缩放前的位置信息
let clipBoxBeforeScaleClipX = 0
let clipBoxBeforeScaleClipY = 0
// 裁剪框缩放前的宽高
let clipBoxBeforeScaleWidth = 0
let clipBoxBeforeScaleHeight = 0
// 裁剪框缩放前点击鼠标的Page(X|Y)位置信息
let clipBoxBeforeScalePageX = 0
let clipBoxBeforeScalePageY = 0
// 以下为裁剪框移动时临时变量
// 裁剪框移动鼠标内部位置信息
let clipBoxMoveInnerX = 0
let clipBoxMoveInnerY = 0
// 图片在窗口面板尺寸小的缩放比
let xScale = 1
let yScale = 1
// 创建页面实例对象
Page({
// 页面的初始数据
data: {
// 当前图片路径
imagePath: '',
originWidth: 0,
originHeight: 0,
// 图片显示面板宽高
panelWidth: 0,
panelHeight: 0,
// 裁剪区域宽高
clipWidth: 0,
clipHeight: 0,
// 裁剪区域位置信息
clipX: 0,
clipY: 0,
// 裁剪区域底图位置信息
clipImgX: 0,
clipImgY: 0,
// 正要裁剪的宽高
croppingImageWidth: 0,
croppingImageHeight: 0,
},
onLoad() {
this.ctx = wx.createCameraContext();
},
onReady() {
this.init()
},
onShow: function () {
var that = this
wx.authorize({
scope: 'scope.camera',
success: function (res) {
that.setData({
isShowCamera: true,
})
},
fail: function (res) {
console.log("" + res);
wx.showModal({
title: '请求授权您的摄像头',
content: '如需正常使用此小程序功能,请您按确定并在设置页面授权用户信息',
confirmText: '确定',
success: res => {
if (res.confirm) {
wx.openSetting({
success: function (res) {
console.log('成功');
console.log(res);
if (res.authSetting['scope.camera']) { //设置允许获取摄像头
console.log('设置允许获取摄像头')
wx.showToast({
title: '授权成功',
icon: 'success',
duration: 1000
})
that.setData({
isShowCamera: true,
})
} else { //不允许
wx.showToast({
title: '授权失败',
icon: 'none',
duration: 1000
})
wx.navigateBack({ delta: 1 })
}
}
})
} else { //取消
wx.showToast({
title: '授权失败',
icon: 'none',
duration: 1000
})
wx.navigateBack({ delta: 1 })
}
}
})
}
})
},
init() {
preCtx = wx.createCanvasContext('main')
},
// 版本3.0
/**
* 相册选择图片
*/
xiangcePic: function () {
var that = this;
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album'],
sizeType: ['original', 'compressed'],
success: function (res) {
// 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
var options = res.tempFiles[0].tempFilePath;
//初始化参数
that.setData({
imagePath: '',
})
that.syscImagePath(options);
}, fail: function (res) {
console.log(res)
}
})
},
/**
* 拍照
*/
takePhotoAction: function () {
var that = this
wx.showLoading({
title: '请稍后',
mask: true,
})
that.ctx.takePhoto({
quality: 'high', //高质量
success: (res) => {
//初始化参数
that.setData({
imagePath: '',
})
that.syscImagePath(res.tempImagePath);
},
fail(error) {
console.log(error)
}
})
},
syscImagePath(originPath) {
// originPath = event.detail.value
originPath && this.loadImage(originPath)
},
changeImage() {
if (!this.data.imagePath) {
return
}
selectPhoto((fileRes) => {
const tempFiles = fileRes.tempFiles[0]
originPath = tempFiles.tempFilePath || tempFiles.path
wx.hideLoading()
originPath && this.loadImage(originPath)
})
},
//拉取图片信息
loadImage(originPath) {
wx.getImageInfo({
src: originPath,
success: async (imgInfo) => {
this.setData({
originWidth: imgInfo.width,
originHeight: imgInfo.height
})
this.initImage(imgInfo.width, imgInfo.height, originPath)
},
fail: (err) => {
console.log('getImageInfo', err)
}
})
},
//图片等比转化
initImage(imgWidth, imgHeight, imagePath) {
// this.setData({
// clipX: 0,
// clipY: 0,
// clipImgX: 0,
// clipImgY: 0
// })
// 初始化图片,根据窗口大小,设置图片面板尺寸
let panelW = sys.windowWidth - 20
let panelH = sys.windowHeight - 175
if (panelH / panelW >= imgHeight / imgWidth) {
panelH = parseInt(panelW * imgHeight / imgWidth)
} else {
panelW = parseInt(panelH * imgWidth / imgHeight)
}
//重新拉取截取框的宽高以及坐标
var clipWidth = parseInt(panelW * 0.8);
var clipHeight = parseInt((panelW / 355) * 124);
var clipX = parseInt(((panelW - (0.8 * panelW)) / 2));
var clipY = parseInt((panelH - clipHeight) / 2);
var clipImgX = 0 - parseInt(((panelW - (0.8 * panelW)) / 2));
var clipImgY = 0 - parseInt(((panelH - clipHeight) / 2));
imageWidth = imgWidth
imageHeight = imgHeight
xScale = panelW / imageWidth
yScale = panelH / imageHeight
wx.hideLoading();
this.setData({
imagePath,
panelWidth: panelW,
panelHeight: panelH,
clipWidth,//当前宽度的80%
clipHeight,//当前高度
clipX,//截取框左上x坐标
clipY,//截取框左上y坐标
clipImgX,//截取框底图左上x坐标
clipImgY,//截取框左上y坐标
croppingImageWidth: parseInt(clipWidth / xScale),//画布剪切宽度
croppingImageHeight: parseInt(clipHeight / yScale)//画布剪切高度
})
},
touchstartM(event) {
const { clipX, clipY } = this.data
const { pageX, pageY } = event.touches[0]
// 获取鼠标点在裁剪框的内部位置信息
clipBoxMoveInnerX = pageX - clipX
clipBoxMoveInnerY = pageY - clipY
},
touchmoveM(event) {
const { pageX, pageY } = event.touches[0]
const { panelWidth, panelHeight, clipHeight, clipWidth } = this.data
// 裁剪框不能脱离面板
// X位置范围为 0 到 (面板宽度-裁剪框宽度)
let clipX = pageX - clipBoxMoveInnerX
clipX = Math.max(clipX, 0)
const panelX = panelWidth - clipWidth
clipX = Math.min(clipX, panelX)
// Y位置范围为 0 到 (面板高度-裁剪框高度)
let clipY = pageY - clipBoxMoveInnerY
clipY = Math.max(clipY, 0)
const panleY = panelHeight - clipHeight
clipY = Math.min(clipY, panleY)
// 裁剪框底图位置信息
const clipImgX = 0 - clipX
const clipImgY = 0 - clipY
this.setData({
clipX,
clipY,
clipImgX,
clipImgY
})
},
// 处理缩放动作中不同corner时的尺寸位置信息
getClipX(clipWidth) {
switch (activeCorner) {
case 'leftTop':
case 'leftBottom':
return clipBoxBeforeScaleClipX + (clipBoxBeforeScaleWidth - clipWidth)
case 'rightTop':
case 'rightBottom':
return clipBoxBeforeScaleClipX;
default:
return 0
}
},
getClipY(clipHeight) {
switch (activeCorner) {
case 'leftTop':
case 'rightTop':
return clipBoxBeforeScaleClipY + (clipBoxBeforeScaleHeight - clipHeight)
case 'leftBottom':
case 'rightBottom':
return clipBoxBeforeScaleClipY
default:
return 0
}
},
getScaleXWidthOffset(offsetW) {
switch (activeCorner) {
case 'leftTop':
case 'leftBottom':
return -offsetW
case 'rightTop':
case 'rightBottom':
return offsetW
default:
return 0
}
},
getScaleYHeightOffset(offsetH) {
switch (activeCorner) {
case 'rightBottom':
case 'leftBottom':
return offsetH
case 'rightTop':
case 'leftTop':
return -offsetH
default:
return 0
}
},
touchstart(event) {
const dragId = event.currentTarget.dataset.id
const { pageX, pageY } = event.touches[0]
const { clipX, clipY, clipHeight, clipWidth } = this.data
// 设置缩放时临时变量初始化值
activeCorner = dragId
clipBoxBeforeScalePageX = pageX
clipBoxBeforeScalePageY = pageY
clipBoxBeforeScaleClipX = clipX
clipBoxBeforeScaleClipY = clipY
clipBoxBeforeScaleWidth = clipWidth
clipBoxBeforeScaleHeight = clipHeight
},
touchmove(event) {
const { pageX, pageY } = event.touches[0]
const { panelWidth, panelHeight } = this.data
// 缩放在X上的偏移
const xWidthOffset = this.getScaleXWidthOffset(pageX - clipBoxBeforeScalePageX)
// 裁剪框最小宽度36
let clipWidth = Math.max(clipBoxBeforeScaleWidth + xWidthOffset, 36)
// 设置缩放最大宽度,放大时不能超过面板、缩小时不能超过初始裁剪框
let tempPanelWidth = pageX > clipBoxBeforeScalePageX ? panelWidth - clipBoxBeforeScaleClipX : clipBoxBeforeScaleWidth + clipBoxBeforeScaleClipX
// 设置裁剪框宽度
clipWidth = Math.min(clipWidth, tempPanelWidth)
// 缩放在Y上的偏移
const yHeightOffset = this.getScaleYHeightOffset(pageY - clipBoxBeforeScalePageY)
// 裁剪框最小高度36
let clipHeight = Math.max(clipBoxBeforeScaleHeight + yHeightOffset, 36)
// 设置缩放最大高度,放大时不能超过面板、缩小时不能超过初始裁剪框
let tempPanelHeight = pageY > clipBoxBeforeScalePageY ? panelHeight - clipBoxBeforeScaleClipY : clipBoxBeforeScaleHeight + clipBoxBeforeScaleClipY
// 设置裁剪框高度
clipHeight = Math.min(clipHeight, tempPanelHeight)
// 裁剪框位置信息
let clipX = this.getClipX(clipWidth)
let clipY = this.getClipY(clipHeight)
// 裁剪框底图位置信息
let clipImgX = 0 - clipX
let clipImgY = 0 - clipY
this.setData({
clipWidth,
clipHeight,
clipX,
clipY,
clipImgX,
clipImgY,
croppingImageWidth: parseInt(clipWidth / xScale),
croppingImageHeight: parseInt(clipHeight / yScale)
})
},
//下载裁剪
downloadCanvasImage() {
const { imagePath } = this.data
if (!imagePath) {
return
}
wx.showLoading({ title: '保存中...' })
//下载方法以去除
//savePhoto(imagePath)
},
//确认裁剪
getCropperImage() {
const { imagePath, croppingImageWidth, croppingImageHeight, panelHeight, panelWidth, clipImgX, clipImgY } = this.data
if (!imagePath) {
return
}
wx.showLoading({ title: '正在裁剪...' })
preCtx.clearRect(0, 0, imageWidth, imageHeight)
const width = croppingImageWidth
const height = croppingImageHeight
const xPos = Math.abs(clipImgX / xScale)
const yPos = Math.abs(clipImgY / yScale)
preCtx.drawImage(imagePath, xPos, yPos, width, height, 0, 0, width, height)
preCtx.save()
preCtx.restore()
const that = this
preCtx.draw(false, function () {
setTimeout(() => {
wx.canvasToTempFilePath({
x: 0,
y: 0,
width,
height,
destWidth: width,
destHeight: height,
canvasId: 'main',
success: (canRes) => {
wx.hideLoading()
console.log(canRes.tempFilePath)
//裁剪完成后,做存储,做记录。后返回网络图片跳转搜索页面
that.initImage(width, height, canRes.tempFilePath)//展示切图效果
},
fail(err) {
wx.hideLoading()
console.log(err)
}
})
}, 200)
})
},
//恢复原图
getOriginImage() {
if (!originPath) {
return
}
const { originWidth, originHeight } = this.data
this.initImage(originWidth, originHeight, originPath)
},
//关闭裁剪页面
caijian_Close: function () {
var that = this
that.setData({
imagePath: ''
})
},
//关闭拍照页面
paizhao_Close: function () {
wx.navigateBack({
delta: 1 //返回指定页面
})
},
})
二:.json文件
{
"navigationBarTitleText": "拍照"
}
三:.wxml文件
<block wx:if="{{!imagePath}}">
<camera wx:if='{{isShowCamera}}' class="camera-box" devic-position="width" flash="off" catchtouchmove='ture'>
<cover-view class='camera-box flex center'>
<cover-image class="pai_image" src="/pages/images/ico/ico_pai_zhong.png"></cover-image>
</cover-view>
</camera>
<cover-view class='camerabgImage_foot flex jcb acenter'>
<cover-view class="flex cha" bindtap="paizhao_Close">
<cover-image class="cha_image" src="/pages/images/ico/pai_cha.png"></cover-image>
</cover-view>
<cover-view class="flex btn2" bindtap="takePhotoAction">
<cover-image class="btn_image" src="/pages/images/ico/ico_paizhao.png"></cover-image>
</cover-view>
<cover-view class="flex xiancge" bindtap="xiangcePic">
<cover-image class="btn_image" src="/pages/images/ico/pai_xoangce.png"></cover-image>
</cover-view>
</cover-view>
</block>
<!-- 画布 -->
<view hidden="{{!imagePath?true:false}}" class="flex canvas_box jcenter">
<view class="crop-container">
<view class="crop-content" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}};">
<image src="{{imagePath}}" class="pos-absolute" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: 0; top:0;" />
<view class="mask-bg" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: 0; top: 0;"></view>
<view class="pos-absolute clip-path" style="width: {{clipWidth + 'px'}}; height: {{clipHeight + 'px'}}; left: {{clipX + 'px'}}; top: {{clipY + 'px'}};">
<image src="{{imagePath}}" class="pos-absolute" style="width: {{panelWidth + 'px'}}; height: {{panelHeight + 'px'}}; left: {{clipImgX + 'px'}}; top: {{clipImgY + 'px'}};" />
</view>
<view bind:touchmove="touchmoveM" bind:touchstart="touchstartM" class="pos-absolute" style="width: {{clipWidth + 'px'}}; height: {{clipHeight + 'px'}}; left: {{clipX + 'px'}}; top: {{clipY + 'px'}};">
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="leftTop" class="corner-area left-top"></view>
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="rightTop" class="corner-area right-top"></view>
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="rightBottom" class="corner-area right-bottom"></view>
<view capture-catch:touchmove="touchmove" capture-catch:touchstart="touchstart" data-id="leftBottom" class="corner-area left-bottom"></view>
</view>
</view>
</view>
<view class="flex jcb acenter canvas_foot">
<view class="flex cha" bindtap="caijian_Close">
<image class="cha_image" src="/pages/images/ico/pai_cha.png" />
</view>
<view class="flex btn2" bindtap="getCropperImage">
<image class="btn_image" src="/pages/images/ico/ico_paizhao2.png" />
</view>
<view class="flex xiancge" bindtap="xiangcePic">
<image class="btn_image" src="/pages/images/ico/pai_xoangce2.png" />
</view>
</view>
<canvas id="main" canvasId="main" class="main-canvas" style="width: {{croppingImageWidth + 'px'}}; height: {{croppingImageHeight + 'px'}}"></canvas>
</view>
四:.wxss文件
.camera-box {
width: 100vw;
height: calc(100vh - 300rpx);
}
.camera-box .pai_image {
width: 600rpx;
height: 248rpx;
margin: auto;
}
/* 新版本样式 */
.camerabgImage_foot {
position: fixed;
bottom: 0rpx;
left: 0rpx;
width: calc(100% - 138rpx);
height: 300rpx;
z-index: 999;
background-color: #000;
padding: 0 70rpx;
}
.camerabgImage_foot .cha .cha_image {
width: 80rpx;
height: 80rpx;
}
.camerabgImage_foot .btn2 .btn_image {
width: 118rpx;
height: 118rpx;
}
.camerabgImage_foot .xiancge .btn_image {
width: 80rpx;
height: 80rpx;
}
/* 画布底部 */
.canvas_box {
background-color: #000;
width: 100vw;
height: calc(100vh - 300rpx);
}
.canvas_box .canvas {
margin: 40rpx auto 0 auto;
}
.canvas_foot {
position: fixed;
bottom: 0rpx;
left: 0rpx;
width: calc(100% - 138rpx);
height: 300rpx;
z-index: 997;
background-color: #000;
padding: 0 70rpx;
}
.canvas_foot .cha .cha_image {
width: 80rpx;
height: 80rpx;
}
.canvas_foot .btn2 .btn_image {
width: 118rpx;
height: 118rpx;
}
.canvas_foot .xiancge .btn_image {
width: 80rpx;
height: 80rpx;
}
/* 新版本2.0 */
.main-canvas {
position: fixed;
top: -9999px;
left: -9999px;
}
.image-crop {
position: relative;
height: 100vh;
}
.add-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 360px;
background: #f2f2f2;
}
.crop-container {
display: flex;
justify-content: center;
padding-top: 20rpx;
}
.crop-content {
position: relative;
}
.pos-absolute {
position: absolute;
border-radius: 10rpx;
}
.mask-bg {
position: absolute;
left: 0;
top: 0;
opacity: 0.6;
background: #666;
}
.clip-path {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
background: transparent;
border-radius: 10rpx;
}
.corner-area {
position: absolute;
width: 40rpx;
height: 40rpx;
/* background: rgba(255, 0, 0, 0.2); */
}
.left-top {
border-top: 2px solid #fff;
border-left: 2px solid #fff;
left: -2px;
top: -2px;
border-radius: 10rpx 0 0 0;
}
.right-top {
border-top: 2px solid #fff;
border-right: 2px solid #fff;
right: 0px;
top: -2px;
border-radius: 0 10rpx 0 0;
}
.right-bottom {
border-bottom: 2px solid #fff;
border-right: 2px solid #fff;
right: 0px;
bottom: -2px;
border-radius: 0 0 10rpx 0;
}
.left-bottom {
border-bottom: 2px solid #fff;
border-left: 2px solid #fff;
left: -2px;
bottom: -2px;
border-radius: 0 0 0 10rpx;
}
.confirm-area {
position: relative;
display: flex;
align-items: center;
width: 100%;
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin-top: 20px;
padding: 5px 0;
}
.cut-size {
font-size: 12px;
margin-left: 10px;
}
.cut-size-value {
color: red;
}
.btn-cut {
color: #4cae4c;
padding: 3px;
border: 1px solid #4cae4c;
border-radius: 5px;
}
.done {
margin-left: 20px;
font-size: 14px;
}
.origin {
position: absolute;
right: 15px;
font-size: 12px;
}
.btn-operate {
width: 100%;
display: flex;
justify-content: center;
margin-top: 15px;
}
.btn {
position: relative;
width: 140px;
height: 30px;
line-height: 30px;
text-align: center;
padding: 2px 0;
font-size: 16px;
border-radius: 10px;
}
.btn-default {
color: #000;
background-color: #f8f8f8;
border: 1px solid rgba(0, 0, 0, 0.2);
margin: 0 8px;
}
.btn-primary {
color: #ffffff;
background-color: #4cae4c;
border: 1px solid #4cae4c;
margin: 0 8px;
}
.main-canvas {
position: fixed;
top: -9999px;
left: -9999px;
}