cropper组件
cropper.js
// component/cropper/cropper.js
const device = wx.getSystemInfoSync();
var twoPoint = {
x1: 0,
y1: 0,
x2: 0,
y2: 0
}
Component({
/**
* 组件的属性列表
*/
properties: {
ratio: {
type: Number,
observer: function (newVal, oldVal) {
this.setData({
width: device.windowWidth * 0.8,
height: device.windowWidth * 0.8 / newVal
})
}
},
url: {
type: String,
observer ( newVal, oldVal ) {
this.initImg( newVal )
}
}
},
/**
* 组件的初始数据
*/
data: {
width: device.windowWidth * 0.8, //剪裁框的宽度
height: device.windowWidth * 0.8 / (598 / 790), //剪裁框的长度
originImg: null, //存放原图信息
stv: {
offsetX: 0, //剪裁图片左上角坐标x
offsetY: 0, //剪裁图片左上角坐标y
zoom: false, //是否缩放状态
distance: 0, //两指距离
scale: 1, //缩放倍数
rotate: 0 //旋转角度
},
},
/**
* 组件的方法列表
*/
methods: {
uploadTap() {
//上传本地图片
let _this = this
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success(res) {
_this.initImg( res.tempFilePaths[0]);
}
})
},
rotate() {
let _this = this;
_this.setData({
'stv.rotate': _this.data.stv.rotate % 90 == 0 ? _this.data.stv.rotate = _this.data.stv.rotate + 90 : _this.data.stv.rotate = 0
})
},
cropperImg() {
// canvas剪裁图片并导出
wx.showLoading({
//显示loading
title: 'loading',
mask: true //显示透明蒙层,防止触摸穿透
})
let _this = this;
let ctx = wx.createCanvasContext('imgcrop',this);
let cropData = _this.data.stv;
ctx.save();
// 缩放偏移值
let x = (_this.data.originImg.width - _this.data.originImg.width * cropData.scale) / 2;
let y = (_this.data.originImg.height - _this.data.originImg.height * cropData.scale) / 2;
//画布中点坐标转移到图片中心
let movex = (cropData.offsetX + x) * 2 + _this.data.originImg.width * cropData.scale;
let movey = (cropData.offsetY + y) * 2 + _this.data.originImg.height * cropData.scale;
ctx.translate(movex, movey); //translate 对坐标原点进行缩放
ctx.rotate(cropData.rotate * Math.PI / 180); //rotate 对坐标轴进行顺时针旋转
ctx.translate(-movex, -movey); //translate 对坐标原点进行缩放
ctx.drawImage(_this.data.originImg.url, (cropData.offsetX + x) * 2, (cropData.offsetY + y) * 2, _this.data.originImg.width * 2 * cropData.scale, _this.data.originImg.height * 2 * cropData.scale);//绘制图像
ctx.restore(); //恢复之前保过的绘图上下文
ctx.draw(false, () => { //进行绘图
wx.canvasToTempFilePath({ //把当前画布指定区域的内容导出生成指定大小的图片
canvasId: 'imgcrop',
success(response) {
console.log(response.tempFilePath);
_this.triggerEvent("getCropperImg", { url: response.tempFilePath })
wx.hideLoading();
},
fail( e ) {
console.log( e );
wx.hideLoading();
wx.showToast({
title: '生成图片失败',
icon: 'none'
})
}
}, this)
});
},
initImg(url) { //定位图片左上角的坐标
let _this = this;
wx.getImageInfo({
src: url,
success(resopne) {
console.log(resopne);
let innerAspectRadio = resopne.width / resopne.height;
if (innerAspectRadio < _this.data.width / _this.data.height) {
_this.setData({
originImg: {
url: url,
width: _this.data.width,
height: _this.data.width / innerAspectRadio
},
stv: {
offsetX: 0,
offsetY: 0 - Math.abs((_this.data.height - _this.data.width / innerAspectRadio) / 2),
zoom: false, //是否缩放状态
distance: 0, //两指距离
scale: 1, //缩放倍数
rotate: 0
},
})
} else {
_this.setData({
originImg: {
url: url,
height: _this.data.height,
width: _this.data.height * innerAspectRadio
},
stv: {
offsetX: 0 - Math.abs((_this.data.width - _this.data.height * innerAspectRadio) / 2),
offsetY: 0,
zoom: false, //是否缩放状态
distance: 0, //两指距离
scale: 1, //缩放倍数
rotate: 0
}
})
}
}
})
},
//事件处理函数
touchstartCallback: function (e) {
if (e.touches.length === 1) { //一指触控
let { clientX, clientY } = e.touches[0];
this.startX = clientX; //手指起始点横坐标
this.startY = clientY; //手指起始点纵坐标
this.touchStartEvent = e.touches;
} else { //多指
let xMove = e.touches[1].clientX - e.touches[0].clientX; //两手指起始点横坐标差
let yMove = e.touches[1].clientY - e.touches[0].clientY; //两手指起始点纵坐标差
let distance = Math.sqrt(xMove * xMove + yMove * yMove); //两手指距离
twoPoint.x1 = e.touches[0].pageX * 2 //第一个手指距离文档左上角的x距离
twoPoint.y1 = e.touches[0].pageY * 2 //第一个手指距离文档左上角的y距离
twoPoint.x2 = e.touches[1].pageX * 2 //第二个手指距离文档左上角的x距离
twoPoint.y2 = e.touches[1].pageY * 2 //第二个手指距离文档左上角的y距离
this.setData({
'stv.distance': distance,
'stv.zoom': true, //缩放状态
})
}
},
//图片手势动态缩放
touchmoveCallback: function (e) {
let _this = this
fn(_this, e)
},
touchendCallback: function (e) {
//触摸结束
if (e.touches.length === 0) {
this.setData({
'stv.zoom': false, //重置缩放状态
})
}
}
}
})
/**
* fn:延时调用函数
* delay:延迟多长时间
* mustRun:至少多长时间触发一次
*/
var throttle = function (fn, delay, mustRun) {
var timer = null,
previous = null;
return function () {
var now = +new Date(),
context = this,
args = arguments;
if (!previous) previous = now;
var remaining = now - previous;
if (mustRun && remaining >= mustRun) {
fn.apply(context, args);
previous = now;
} else {
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
}
}
}
var touchMove = function (_this, e) {
//触摸移动中
if (e.touches.length === 1) {
//单指移动
if (_this.data.stv.zoom) {
//缩放状态,不处理单指
return;
}
let { clientX, clientY } = e.touches[0];
let offsetX = clientX - _this.startX; //移动
let offsetY = clientY - _this.startY; //移动
_this.startX = clientX; //更新起始点坐标
_this.startY = clientY; //更新起始点坐标
let { stv } = _this.data;
stv.offsetX += offsetX;
stv.offsetY += offsetY;
stv.offsetLeftX = -stv.offsetX;
stv.offsetLeftY = -stv.offsetLeftY;
_this.setData({
stv: stv
});
} else if (e.touches.length === 2) {
//计算旋转
let preTwoPoint = JSON.parse(JSON.stringify(twoPoint))
twoPoint.x1 = e.touches[0].pageX * 2
twoPoint.y1 = e.touches[0].pageY * 2
twoPoint.x2 = e.touches[1].pageX * 2
function vector(x1, y1, x2, y2) {
this.x = x2 - x1;
this.y = y2 - y1;
};
//计算点乘
function calculateVM(vector1, vector2) {
return (vector1.x * vector2.x + vector1.y * vector2.y) / (Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) * Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y));
}
//计算叉乘
function calculateVC(vector1, vector2) {
return (vector1.x * vector2.y - vector2.x * vector1.y) > 0 ? 1 : -1;
}
let vector1 = new vector(preTwoPoint.x1, preTwoPoint.y1, preTwoPoint.x2, preTwoPoint.y2);
let vector2 = new vector(twoPoint.x1, twoPoint.y1, twoPoint.x2, twoPoint.y2);
let cos = calculateVM(vector1, vector2);
let angle = Math.acos(cos) * 180 / Math.PI;
let direction = calculateVC(vector1, vector2);
let _allDeg = direction * angle;
// 双指缩放
let xMove = e.touches[1].clientX - e.touches[0].clientX; //两指x距离
let yMove = e.touches[1].clientY - e.touches[0].clientY; //两指y距离
let distance = Math.sqrt(xMove * xMove + yMove * yMove); //两指距离
let distanceDiff = distance - _this.data.stv.distance; //两指距离变化
let newScale = _this.data.stv.scale + 0.005 * distanceDiff; //得到缩放倍数
if (Math.abs(_allDeg) > 1) {
_this.setData({
'stv.rotate': _this.data.stv.rotate + _allDeg
})
} else {
//双指缩放
let xMove = e.touches[1].clientX - e.touches[0].clientX;
let yMove = e.touches[1].clientY - e.touches[0].clientY;
let distance = Math.sqrt(xMove * xMove + yMove * yMove);
let distanceDiff = distance - _this.data.stv.distance;
let newScale = _this.data.stv.scale + 0.005 * distanceDiff;
if (newScale < 0.2 || newScale > 2.5) {
return;
}
_this.setData({
'stv.distance': distance,
'stv.scale': newScale,
})
}
} else {
return;
}
}
//为touchMove函数节流
const fn = throttle(touchMove, 10, 10);
cropper.json
{
"component": true,
"usingComponents": {}
}
cropper.wxml
<view class="container">
<!-- 剪裁框与初始图片,剪裁框监听用户手势,获取移动缩放旋转值,images通过css样式显示变化 -->
<view class="img" style="width:{{ width }}px; height:{{height}}px" catchtouchstart="touchstartCallback" catchtouchmove="touchmoveCallback" catchtouchend="touchendCallback" >
<image style="transform: translate({{stv.offsetX}}px, {{stv.offsetY}}px) scale({{stv.scale}}) rotate({{ stv.rotate }}deg);width:{{originImg.width}}px; height: {{originImg.height}}px" src="{{ originImg.url }}"></image>
</view>
<view class='footer'>
<view bindtap='uploadTap'>选择图片</view>
<view bindtap='rotate'>旋转</view>
<view bindtap='cropperImg'>打印</view>
</view>
<!-- canvas长宽设为初始图片设置的长款的两倍,使剪裁得到的图片更清晰,也不至于过大 -->
<canvas class='imgcrop' style="width:{{ width * 2 }}px;height:{{ height * 2}}px;" canvas-id='imgcrop'></canvas>
</view>
cropper.wxcc
.container {
position: relative;
width: 100%;
height: 100%;
background: #000;
}
.img {
position: absolute;
top: 5%;
left: 50%;
transform: translateX(-50%);
overflow: hidden;
background: #eee;
}
.img image {
height:400px;
}
.imgcrop {
position: absolute;
left: -50000rpx;
top: -500000rpx;
}
.footer {
position: absolute;
width: 100%;
height: 110rpx;
color: #fff;
background: #000;
bottom: 0;
display: flex;
align-items: center;
justify-content: space-around;
}
.footer view {
width: 30%;
text-align: center;
}
.background {
width: 100%;
height: 100%;
position: absolute;
top: 0;
z-index: -1;
}
index.js
//index.js
//获取应用实例
const app = getApp()
Page({
data: {
ratio: 598 / 790, //剪裁比例
originUrl: '', //原始图片url
cropperResult: '', //变化后结果
base64: '' //base64
},
uploadTap() {
//首次上传本地图片
let _this = this
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success(res) {
_this.setData({
originUrl: res.tempFilePaths[0],//图片的本地路径
cropperResult: ''
})
}
})
},
getCropperImg(e) {
//将原图片url置空,表示已经完成剪裁,剪裁后图片地址储存
this.setData({
originUrl: '',
cropperResult: e.detail.url,
base64: 'data:image/png;base64,' + wx.getFileSystemManager().readFileSync(e.detail.url, "base64")
})
}
})
index.json
{
"usingComponents": {
"cropper": "../../component/cropper/cropper"
}
}
index.wxml
<view class='container'>
<image class='img' mode='widthFix' src="{{ cropperResult }}" wx:if="{{ cropperResult }}"></image>
<view class='cropper' wx:if="{{originUrl}}">
<cropper bind:getCropperImg="getCropperImg" url="{{ originUrl }}" ratio="{{ ratio }}"></cropper>
</view>
<view class='choose-img' wx:else bindtap='uploadTap'>choose Img</view>
<textarea placeholder="{{cropperResult}}"></textarea>
</view>
index.wxss
/**index.wxss**/
page {
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
background: #eee;
overflow: hidden;
}
.cropper {
width: 100%;
height: 100%;
}
.img {
margin: 20rpx auto;
display: block;
background: #fff;
}
.choose-img {
width: 40%;
text-align: center;
padding: 30rpx;
border: 1px solid #fff;
margin: 20rpx auto;
background: #000;
color: #fff;
}
log.js
//logs.js
const util = require('../../utils/util.js')
Page({
data: {
logs: []
},
onLoad: function () {
this.setData({
logs: (wx.getStorageSync('logs') || []).map(log => {
return util.formatTime(new Date(log))
})
})
}
})
log.json
{
"navigationBarTitleText": "查看启动日志"
}
log.wxml
<!--logs.wxml-->
<view class="container log-list">
<block wx:for="{{logs}}" wx:for-item="log">
<text class="log-item">{{index + 1}}. {{log}}</text>
</block>
</view>
log.wxss
.log-list {
display: flex;
flex-direction: column;
padding: 40rpx;
}
.log-item {
margin: 10rpx;
}
app.json
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"sitemapLocation": "sitemap.json"
}