diff --git a/README.md b/README.md index 698c2a9e198a575b421bd341ead7d5d8a413014f..69bce30676fe9684c13340d7932561dcb70ede69 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ Demo地址:https://github.com/wx-plugin/image-cropper-demo +[点击导入代码片段](https://developers.weixin.qq.com/s/dIQvo5mr7cpm) + 如果有什么好的建议欢迎提issues或者提pr ## 进群 | 鼓励作者 diff --git a/demo/app.js b/demo/app.js new file mode 100644 index 0000000000000000000000000000000000000000..c41246d25e2247edcf90c759cf6d880140135211 --- /dev/null +++ b/demo/app.js @@ -0,0 +1,6 @@ +App({ + onLaunch: function () { + + }, + globalData:{} +}) diff --git a/demo/app.json b/demo/app.json new file mode 100644 index 0000000000000000000000000000000000000000..802d4a02fd0ce8a910073019cb6c7f300f949c18 --- /dev/null +++ b/demo/app.json @@ -0,0 +1,13 @@ +{ + "pages":[ + "index/index", + "cropper/cropper" + ], + "window":{ + "backgroundTextStyle":"light", + "navigationBarBackgroundColor": "#fff", + "navigationBarTitleText": "Weixin", + "navigationBarTextStyle":"black" + }, + "sitemapLocation": "sitemap.json" +} diff --git a/demo/app.wxss b/demo/app.wxss new file mode 100644 index 0000000000000000000000000000000000000000..4270e5ec062af83932c8aed43c55c575498b17a9 --- /dev/null +++ b/demo/app.wxss @@ -0,0 +1,4 @@ +page{ + height: 100%; + width: 100%; +} \ No newline at end of file diff --git a/demo/component/image-cropper/image-cropper.js b/demo/component/image-cropper/image-cropper.js new file mode 100644 index 0000000000000000000000000000000000000000..91f3b21844f4924ef76b74c43460dfc4480ee8e6 --- /dev/null +++ b/demo/component/image-cropper/image-cropper.js @@ -0,0 +1,1117 @@ +Component({ + properties: { + /** + * 图片路径 + */ + 'imgSrc': { + type: String + }, + /** + * 裁剪框高度 + */ + 'height': { + type: Number, + value: 200 + }, + /** + * 裁剪框宽度 + */ + 'width': { + type: Number, + value: 200 + }, + /** + * 裁剪框最小尺寸 + */ + 'min_width': { + type: Number, + value: 100 + }, + 'min_height': { + type: Number, + value: 100 + }, + /** + * 裁剪框最大尺寸 + */ + 'max_width': { + type: Number, + value: 300 + }, + 'max_height': { + type: Number, + value: 300 + }, + /** + * 裁剪框禁止拖动 + */ + 'disable_width': { + type: Boolean, + value: false + }, + 'disable_height': { + type: Boolean, + value: false + }, + /** + * 锁定裁剪框比例 + */ + 'disable_ratio': { + type: Boolean, + value: false + }, + /** + * 生成的图片尺寸相对剪裁框的比例 + */ + 'export_scale': { + type: Number, + value: 3 + }, + /** + * 生成的图片质量0-1 + */ + 'quality': { + type: Number, + value: 1 + }, + 'cut_top': { + type: Number, + value: null + }, + 'cut_left': { + type: Number, + value: null + }, + /** + * canvas上边距(不设置默认不显示) + */ + 'canvas_top': { + type: Number, + value: null + }, + /** + * canvas左边距(不设置默认不显示) + */ + 'canvas_left': { + type: Number, + value: null + }, + /** + * 图片宽度 + */ + 'img_width': { + type: null, + value: null + }, + /** + * 图片高度 + */ + 'img_height': { + type: null, + value: null + }, + /** + * 图片缩放比 + */ + 'scale': { + type: Number, + value: 1 + }, + /** + * 图片旋转角度 + */ + 'angle': { + type: Number, + value: 0 + }, + /** + * 最小缩放比 + */ + 'min_scale': { + type: Number, + value: 0.5 + }, + /** + * 最大缩放比 + */ + 'max_scale': { + type: Number, + value: 2 + }, + /** + * 是否禁用旋转 + */ + 'disable_rotate': { + type: Boolean, + value: false + }, + /** + * 是否限制移动范围(剪裁框只能在图片内) + */ + 'limit_move': { + type: Boolean, + value: false + } + }, + data: { + el: 'image-cropper', //暂时无用 + info: wx.getSystemInfoSync(), + MOVE_THROTTLE: null, //触摸移动节流settimeout + MOVE_THROTTLE_FLAG: true, //节流标识 + INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) + INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) + TIME_BG: null, //背景变暗延时函数 + TIME_CUT_CENTER: null, + _touch_img_relative: [{ + x: 0, + y: 0 + }], //鼠标和图片中心的相对位置 + _flag_cut_touch: false, //是否是拖动裁剪框 + _hypotenuse_length: 0, //双指触摸时斜边长度 + _flag_img_endtouch: false, //是否结束触摸 + _flag_bright: true, //背景是否亮 + _canvas_overflow: true, //canvas缩略图是否在屏幕外面 + _canvas_width: 200, + _canvas_height: 200, + origin_x: 0.5, //图片旋转中心 + origin_y: 0.5, //图片旋转中心 + _cut_animation: false, //是否开启图片和裁剪框过渡 + _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距 + _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距 + watch: { + //监听截取框宽高变化 + width(value, that) { + if (value < that.data.min_width) { + that.setData({ + width: that.data.min_width + }); + } + that._computeCutSize(); + }, + height(value, that) { + if (value < that.data.min_height) { + that.setData({ + height: that.data.min_height + }); + } + that._computeCutSize(); + }, + angle(value, that) { + //停止居中裁剪框,继续修改图片位置 + that._moveStop(); + if (that.data.limit_move) { + if (that.data.angle % 90) { + that.setData({ + angle: Math.round(that.data.angle / 90) * 90 + }); + return; + } + } + }, + _cut_animation(value, that) { + //开启过渡300毫秒之后自动关闭 + clearTimeout(that.data._cut_animation_time); + if (value) { + that.data._cut_animation_time = setTimeout(() => { + that.setData({ + _cut_animation: false + }); + }, 300) + } + }, + limit_move(value, that) { + if (value) { + if (that.data.angle % 90) { + that.setData({ + angle: Math.round(that.data.angle / 90) * 90 + }); + } + that._imgMarginDetectionScale(); + !that.data._canvas_overflow && that._draw(); + } + }, + canvas_top(value, that) { + that._canvasDetectionPosition(); + }, + canvas_left(value, that) { + that._canvasDetectionPosition(); + }, + imgSrc(value, that) { + that.pushImg(); + }, + cut_top(value, that) { + that._cutDetectionPosition(); + if (that.data.limit_move) { + !that.data._canvas_overflow && that._draw(); + } + }, + cut_left(value, that) { + that._cutDetectionPosition(); + if (that.data.limit_move) { + !that.data._canvas_overflow && that._draw(); + } + } + } + }, + attached() { + this.data.info = wx.getSystemInfoSync(); + //启用数据监听 + this._watcher(); + this.data.INIT_IMGWIDTH = this.data.img_width; + this.data.INIT_IMGHEIGHT = this.data.img_height; + this.setData({ + _canvas_height: this.data.height, + _canvas_width: this.data.width, + }); + this._initCanvas(); + this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc); + //根据开发者设置的图片目标尺寸计算实际尺寸 + this._initImageSize(); + //设置裁剪框大小>设置图片尺寸>绘制canvas + this._computeCutSize(); + //检查裁剪框是否在范围内 + this._cutDetectionPosition(); + //检查canvas是否在范围内 + this._canvasDetectionPosition(); + //初始化完成 + this.triggerEvent('load', { + cropper: this + }); + }, + methods: { + /** + * 上传图片 + */ + upload() { + let that = this; + wx.chooseImage({ + count: 1, + sizeType: ['original', 'compressed'], + sourceType: ['album', 'camera'], + success(res) { + const tempFilePaths = res.tempFilePaths[0]; + that.pushImg(tempFilePaths); + wx.showLoading({ + title: '加载中...' + }) + } + }) + }, + /** + * 返回图片信息 + */ + getImg(getCallback) { + this._draw(() => { + wx.canvasToTempFilePath({ + width: this.data.width * this.data.export_scale, + height: Math.round(this.data.height * this.data.export_scale), + destWidth: this.data.width * this.data.export_scale, + destHeight: Math.round(this.data.height) * this.data.export_scale, + fileType: 'png', + quality: this.data.quality, + canvasId: this.data.el, + success: (res) => { + getCallback({ + url: res.tempFilePath, + width: this.data.width * this.data.export_scale, + height: this.data.height * this.data.export_scale + }); + } + }, this) + }); + }, + /** + * 设置图片动画 + * { + * x:10,//图片在原有基础上向下移动10px + * y:10,//图片在原有基础上向右移动10px + * angle:10,//图片在原有基础上旋转10deg + * scale:0.5,//图片在原有基础上增加0.5倍 + * } + */ + setTransform(transform) { + if (!transform) return; + if (!this.data.disable_rotate) { + this.setData({ + angle: transform.angle ? this.data.angle + transform.angle : this.data.angle + }); + } + var scale = this.data.scale; + if (transform.scale) { + scale = this.data.scale + transform.scale; + scale = scale <= this.data.min_scale ? this.data.min_scale : scale; + scale = scale >= this.data.max_scale ? this.data.max_scale : scale; + } + this.data.scale = scale; + let cutX = this.data.cut_left; + let cutY = this.data.cut_top; + if (transform.cutX) { + this.setData({ + cut_left: cutX + transform.cutX + }); + this.data.watch.cut_left(null, this); + } + if (transform.cutY) { + this.setData({ + cut_top: cutY + transform.cutY + }); + this.data.watch.cut_top(null, this); + } + this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top; + this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left; + //图像边缘检测,防止截取到空白 + this._imgMarginDetectionScale(); + //停止居中裁剪框,继续修改图片位置 + this._moveDuring(); + this.setData({ + scale: this.data.scale, + _img_top: this.data._img_top, + _img_left: this.data._img_left + }); + !this.data._canvas_overflow && this._draw(); + //可以居中裁剪框了 + this._moveStop(); //结束操作 + }, + /** + * 设置剪裁框位置 + */ + setCutXY(x, y) { + this.setData({ + cut_top: y, + cut_left: x + }); + }, + /** + * 设置剪裁框尺寸 + */ + setCutSize(w, h) { + this.setData({ + width: w, + height: h + }); + this._computeCutSize(); + }, + /** + * 设置剪裁框和图片居中 + */ + setCutCenter() { + let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; + let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; + //顺序不能变 + this.setData({ + _img_top: this.data._img_top - this.data.cut_top + cut_top, + cut_top: cut_top, //截取的框上边距 + _img_left: this.data._img_left - this.data.cut_left + cut_left, + cut_left: cut_left, //截取的框左边距 + }); + }, + _setCutCenter() { + let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; + let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; + this.setData({ + cut_top: cut_top, //截取的框上边距 + cut_left: cut_left, //截取的框左边距 + }); + }, + /** + * 设置剪裁框宽度-即将废弃 + */ + setWidth(width) { + this.setData({ + width: width + }); + this._computeCutSize(); + }, + /** + * 设置剪裁框高度-即将废弃 + */ + setHeight(height) { + this.setData({ + height: height + }); + this._computeCutSize(); + }, + /** + * 是否锁定旋转 + */ + setDisableRotate(value) { + this.data.disable_rotate = value; + }, + /** + * 是否限制移动 + */ + setLimitMove(value) { + this.setData({ + _cut_animation: true, + limit_move: !!value + }); + }, + /** + * 初始化图片,包括位置、大小、旋转角度 + */ + imgReset() { + this.setData({ + scale: 1, + angle: 0, + _img_top: wx.getSystemInfoSync().windowHeight / 2, + _img_left: wx.getSystemInfoSync().windowWidth / 2, + }) + }, + /** + * 加载(更换)图片 + */ + pushImg(src) { + if (src) { + this.setData({ + imgSrc: src + }); + //发现是手动赋值直接返回,交给watch处理 + return; + } + + // getImageInfo接口传入 src: '' 会导致内存泄漏 + + if (!this.data.imgSrc) return; + wx.getImageInfo({ + src: this.data.imgSrc, + success: (res) => { + this.data.imageObject = res; + //图片非本地路径需要换成本地路径 + if (this.data.imgSrc.search(/tmp/) == -1) { + this.setData({ + imgSrc: res.path + }); + } + //计算最后图片尺寸 + this._imgComputeSize(); + if (this.data.limit_move) { + //限制移动,不留空白处理 + this._imgMarginDetectionScale(); + } + this._draw(); + }, + fail: (err) => { + this.setData({ + imgSrc: '' + }); + } + }); + }, + imageLoad(e) { + setTimeout(() => { + this.triggerEvent('imageload', this.data.imageObject); + + }, 1000) + }, + /** + * 设置图片放大缩小 + */ + setScale(scale) { + if (!scale) return; + this.setData({ + scale: scale + }); + !this.data._canvas_overflow && this._draw(); + }, + /** + * 设置图片旋转角度 + */ + setAngle(angle) { + if (!angle) return; + this.setData({ + _cut_animation: true, + angle: angle + }); + this._imgMarginDetectionScale(); + !this.data._canvas_overflow && this._draw(); + }, + _initCanvas() { + //初始化canvas + if (!this.data.ctx) { + this.data.ctx = wx.createCanvasContext("image-cropper", this); + } + }, + /** + * 根据开发者设置的图片目标尺寸计算实际尺寸 + */ + _initImageSize() { + //处理宽高特殊单位 %>px + if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) { + let width = this.data.INIT_IMGWIDTH.replace("%", ""); + this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width; + } + if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) { + let height = this.data.img_height.replace("%", ""); + this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height; + } + }, + /** + * 检测剪裁框位置是否在允许的范围内(屏幕内) + */ + _cutDetectionPosition() { + let _cutDetectionPositionTop = () => { + //检测上边距是否在范围内 + if (this.data.cut_top < 0) { + this.setData({ + cut_top: 0 + }); + } + if (this.data.cut_top > this.data.info.windowHeight - this.data.height) { + this.setData({ + cut_top: this.data.info.windowHeight - this.data.height + }); + } + }, + _cutDetectionPositionLeft = () => { + //检测左边距是否在范围内 + if (this.data.cut_left < 0) { + this.setData({ + cut_left: 0 + }); + } + if (this.data.cut_left > this.data.info.windowWidth - this.data.width) { + this.setData({ + cut_left: this.data.info.windowWidth - this.data.width + }); + } + }; + //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中) + if (this.data.cut_top == null && this.data.cut_left == null) { + this._setCutCenter(); + } else if (this.data.cut_top != null && this.data.cut_left != null) { + _cutDetectionPositionTop(); + _cutDetectionPositionLeft(); + } else if (this.data.cut_top != null && this.data.cut_left == null) { + _cutDetectionPositionTop(); + this.setData({ + cut_left: (this.data.info.windowWidth - this.data.width) / 2 + }); + } else if (this.data.cut_top == null && this.data.cut_left != null) { + _cutDetectionPositionLeft(); + this.setData({ + cut_top: (this.data.info.windowHeight - this.data.height) / 2 + }); + } + }, + /** + * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染 + * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外 + */ + _canvasDetectionPosition() { + if (this.data.canvas_top == null && this.data.canvas_left == null) { + this.data._canvas_overflow = false; + this.setData({ + canvas_top: -5000, + canvas_left: -5000 + }); + } else if (this.data.canvas_top != null && this.data.canvas_left != null) { + if (this.data.canvas_top < -this.data.height || this.data.canvas_top > this.data.info.windowHeight) { + this.data._canvas_overflow = true; + } else { + this.data._canvas_overflow = false; + } + } else if (this.data.canvas_top != null && this.data.canvas_left == null) { + this.setData({ + canvas_left: 0 + }); + } else if (this.data.canvas_top == null && this.data.canvas_left != null) { + this.setData({ + canvas_top: 0 + }); + if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) { + this.data._canvas_overflow = true; + } else { + this.data._canvas_overflow = false; + } + } + }, + /** + * 图片边缘检测-位置 + */ + _imgMarginDetectionPosition(scale) { + if (!this.data.limit_move) return; + let left = this.data._img_left; + let top = this.data._img_top; + var scale = scale || this.data.scale; + let img_width = this.data.img_width; + let img_height = this.data.img_height; + if (this.data.angle / 90 % 2) { + img_width = this.data.img_height; + img_height = this.data.img_width; + } + left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2; + left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2; + top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2; + top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2; + this.setData({ + _img_left: left, + _img_top: top, + scale: scale + }) + }, + /** + * 图片边缘检测-缩放 + */ + _imgMarginDetectionScale() { + if (!this.data.limit_move) return; + let scale = this.data.scale; + let img_width = this.data.img_width; + let img_height = this.data.img_height; + if (this.data.angle / 90 % 2) { + img_width = this.data.img_height; + img_height = this.data.img_width; + } + if (img_width * scale < this.data.width) { + scale = this.data.width / img_width; + } + if (img_height * scale < this.data.height) { + scale = Math.max(scale, this.data.height / img_height); + } + this._imgMarginDetectionPosition(scale); + }, + _setData(obj) { + let data = {}; + for (var key in obj) { + if (this.data[key] != obj[key]) { + data[key] = obj[key]; + } + } + this.setData(data); + return data; + }, + /** + * 计算图片尺寸 + */ + _imgComputeSize() { + let img_width = this.data.img_width, + img_height = this.data.img_height; + if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { + //默认按图片最小边 = 对应裁剪框尺寸 + img_width = this.data.imageObject.width; + img_height = this.data.imageObject.height; + if (img_width / img_height > this.data.width / this.data.height) { + img_height = this.data.height; + img_width = this.data.imageObject.width / this.data.imageObject.height * img_height; + } else { + img_width = this.data.width; + img_height = this.data.imageObject.height / this.data.imageObject.width * img_width; + } + } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { + img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT; + } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) { + img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH; + } + this.setData({ + img_width: img_width, + img_height: img_height + }); + }, + //改变截取框大小 + _computeCutSize() { + if (this.data.width > this.data.info.windowWidth) { + this.setData({ + width: this.data.info.windowWidth, + }); + } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) { + this.setData({ + cut_left: this.data.info.windowWidth - this.data.cut_left, + }); + }; + if (this.data.height > this.data.info.windowHeight) { + this.setData({ + height: this.data.info.windowHeight, + }); + } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) { + this.setData({ + cut_top: this.data.info.windowHeight - this.data.cut_top, + }); + }!this.data._canvas_overflow && this._draw(); + }, + //开始触摸 + _start(event) { + this.data._flag_img_endtouch = false; + if (event.touches.length == 1) { + //单指拖动 + this.data._touch_img_relative[0] = { + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + } + } else { + //双指放大 + let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX); + let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY); + this.data._touch_img_relative = [{ + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + }, { + x: (event.touches[1].clientX - this.data._img_left), + y: (event.touches[1].clientY - this.data._img_top) + }]; + this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + }!this.data._canvas_overflow && this._draw(); + }, + _move_throttle() { + //安卓需要节流 + if (this.data.info.platform == 'android') { + clearTimeout(this.data.MOVE_THROTTLE); + this.data.MOVE_THROTTLE = setTimeout(() => { + this.data.MOVE_THROTTLE_FLAG = true; + }, 1000 / 40) + return this.data.MOVE_THROTTLE_FLAG; + } else { + this.data.MOVE_THROTTLE_FLAG = true; + } + }, + _move(event) { + if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return; + this.data.MOVE_THROTTLE_FLAG = false; + this._move_throttle(); + this._moveDuring(); + if (event.touches.length == 1) { + //单指拖动 + let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x), + top = (event.touches[0].clientY - this.data._touch_img_relative[0].y); + //图像边缘检测,防止截取到空白 + this.data._img_left = left; + this.data._img_top = top; + this._imgMarginDetectionPosition(); + this.setData({ + _img_left: this.data._img_left, + _img_top: this.data._img_top + }); + } else { + //双指放大 + let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)), + height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)), + hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)), + scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length), + current_deg = 0; + scale = scale <= this.data.min_scale ? this.data.min_scale : scale; + scale = scale >= this.data.max_scale ? this.data.max_scale : scale; + //图像边缘检测,防止截取到空白 + this.data.scale = scale; + this._imgMarginDetectionScale(); + //双指旋转(如果没禁用旋转) + let _touch_img_relative = [{ + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + }, { + x: (event.touches[1].clientX - this.data._img_left), + y: (event.touches[1].clientY - this.data._img_top) + }]; + if (!this.data.disable_rotate) { + let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x); + let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x); + let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x); + let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x); + //当前旋转的角度 + let first_deg = first_atan - first_atan_old, + second_deg = second_atan - second_atan_old; + if (first_deg != 0) { + current_deg = first_deg; + } else if (second_deg != 0) { + current_deg = second_deg; + } + } + this.data._touch_img_relative = _touch_img_relative; + this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + //更新视图 + this.setData({ + angle: this.data.angle + current_deg, + scale: this.data.scale + }); + }!this.data._canvas_overflow && this._draw(); + }, + //结束操作 + _end(event) { + this.data._flag_img_endtouch = true; + this._moveStop(); + }, + //点击中间剪裁框处理 + _click(event) { + if (!this.data.imgSrc) { + //调起上传 + this.upload(); + return; + } + this._draw(() => { + let x = event.detail ? event.detail.x : event.touches[0].clientX; + let y = event.detail ? event.detail.y : event.touches[0].clientY; + if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) { + //生成图片并回调 + wx.canvasToTempFilePath({ + width: this.data.width * this.data.export_scale, + height: Math.round(this.data.height * this.data.export_scale), + destWidth: this.data.width * this.data.export_scale, + destHeight: Math.round(this.data.height) * this.data.export_scale, + fileType: 'png', + quality: this.data.quality, + canvasId: this.data.el, + success: (res) => { + this.triggerEvent('tapcut', { + url: res.tempFilePath, + width: this.data.width * this.data.export_scale, + height: this.data.height * this.data.export_scale + }); + } + }, this) + } + }); + }, + //渲染 + _draw(callback) { + if (!this.data.imgSrc) return; + let draw = () => { + //图片实际大小 + let img_width = this.data.img_width * this.data.scale * this.data.export_scale; + let img_height = this.data.img_height * this.data.scale * this.data.export_scale; + //canvas和图片的相对距离 + var xpos = this.data._img_left - this.data.cut_left; + var ypos = this.data._img_top - this.data.cut_top; + //旋转画布 + this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale); + this.data.ctx.rotate(this.data.angle * Math.PI / 180); + this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height); + this.data.ctx.draw(false, () => { + callback && callback(); + }); + } + if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) { + //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方 + this.setData({ + _canvas_height: this.data.height, + _canvas_width: this.data.width, + }, () => { + //延迟40毫秒防止点击过快出现拉伸或裁剪过多 + setTimeout(() => { + draw(); + }, 40); + }); + } else { + draw(); + } + }, + //裁剪框处理 + _cutTouchMove(e) { + if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) { + if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return; + //节流 + this.data.MOVE_THROTTLE_FLAG = false; + this._move_throttle(); + let width = this.data.width, + height = this.data.height, + cut_top = this.data.cut_top, + cut_left = this.data.cut_left, + size_correct = () => { + width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width; + height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height; + }, + size_inspect = () => { + if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) { + size_correct(); + return false; + } else { + size_correct(); + return true; + } + }; + height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY)); + switch (this.data.CUT_START.corner) { + case 1: + width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width); + break + case 2: + width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height) + cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width) + break + case 3: + width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height); + break + case 4: + width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + break + } + if (!this.data.disable_width && !this.data.disable_height) { + this.setData({ + width: width, + cut_left: cut_left, + height: height, + cut_top: cut_top, + }) + } else if (!this.data.disable_width) { + this.setData({ + width: width, + cut_left: cut_left + }) + } else if (!this.data.disable_height) { + this.setData({ + height: height, + cut_top: cut_top + }) + } + this._imgMarginDetectionScale(); + } + }, + _cutTouchStart(e) { + let currentX = e.touches[0].clientX; + let currentY = e.touches[0].clientY; + let cutbox_top4 = this.data.cut_top + this.data.height - 30; + let cutbox_bottom4 = this.data.cut_top + this.data.height + 20; + let cutbox_left4 = this.data.cut_left + this.data.width - 30; + let cutbox_right4 = this.data.cut_left + this.data.width + 30; + + let cutbox_top3 = this.data.cut_top - 30; + let cutbox_bottom3 = this.data.cut_top + 30; + let cutbox_left3 = this.data.cut_left + this.data.width - 30; + let cutbox_right3 = this.data.cut_left + this.data.width + 30; + + let cutbox_top2 = this.data.cut_top - 30; + let cutbox_bottom2 = this.data.cut_top + 30; + let cutbox_left2 = this.data.cut_left - 30; + let cutbox_right2 = this.data.cut_left + 30; + + let cutbox_top1 = this.data.cut_top + this.data.height - 30; + let cutbox_bottom1 = this.data.cut_top + this.data.height + 30; + let cutbox_left1 = this.data.cut_left - 30; + let cutbox_right1 = this.data.cut_left + 30; + if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + x: currentX, + y: currentY, + corner: 4 + } + } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + x: currentX, + y: currentY, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + corner: 3 + } + } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + x: currentX, + y: currentY, + corner: 2 + } + } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + x: currentX, + y: currentY, + corner: 1 + } + } + }, + _cutTouchEnd(e) { + this._moveStop(); + this.data._flag_cut_touch = false; + }, + //停止移动时需要做的操作 + _moveStop() { + //清空之前的自动居中延迟函数并添加最新的 + clearTimeout(this.data.TIME_CUT_CENTER); + this.data.TIME_CUT_CENTER = setTimeout(() => { + //动画启动 + if (!this.data._cut_animation) { + this.setData({ + _cut_animation: true + }); + } + this.setCutCenter(); + }, 1000) + //清空之前的背景变化延迟函数并添加最新的 + clearTimeout(this.data.TIME_BG); + this.data.TIME_BG = setTimeout(() => { + if (this.data._flag_bright) { + this.setData({ + _flag_bright: false + }); + } + }, 2000) + }, + //移动中 + _moveDuring() { + //清空之前的自动居中延迟函数 + clearTimeout(this.data.TIME_CUT_CENTER); + //清空之前的背景变化延迟函数 + clearTimeout(this.data.TIME_BG); + //高亮背景 + if (!this.data._flag_bright) { + this.setData({ + _flag_bright: true + }); + } + }, + //监听器 + _watcher() { + Object.keys(this.data).forEach(v => { + this._observe(this.data, v, this.data.watch[v]); + }) + }, + _observe(obj, key, watchFun) { + var val = obj[key]; + Object.defineProperty(obj, key, { + configurable: true, + enumerable: true, + set: (value) => { + val = value; + watchFun && watchFun(val, this); + }, + get() { + if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) { + let ret = parseFloat(parseFloat(val).toFixed(3)); + if (typeof val == "string" && val.indexOf("%") != -1) { + ret += '%'; + } + return ret; + } + return val; + } + }) + }, + _preventTouchMove() {} + } +}) \ No newline at end of file diff --git a/demo/component/image-cropper/image-cropper.json b/demo/component/image-cropper/image-cropper.json new file mode 100644 index 0000000000000000000000000000000000000000..d577adeaf94bed8407ab4f2560b1ddff63dd8bdd --- /dev/null +++ b/demo/component/image-cropper/image-cropper.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/demo/component/image-cropper/image-cropper.wxml b/demo/component/image-cropper/image-cropper.wxml new file mode 100644 index 0000000000000000000000000000000000000000..a4a752685797d8389f0cbd4ee20888d6108d11c6 --- /dev/null +++ b/demo/component/image-cropper/image-cropper.wxml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/component/image-cropper/image-cropper.wxss b/demo/component/image-cropper/image-cropper.wxss new file mode 100644 index 0000000000000000000000000000000000000000..80eb895801fca31e1a4885ff8b5ff9fedac03992 --- /dev/null +++ b/demo/component/image-cropper/image-cropper.wxss @@ -0,0 +1,143 @@ +.image-cropper { + background: rgba(14, 13, 13, .8); + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1; +} + +.image-cropper .main { + position: absolute; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +.image-cropper .content { + z-index: 9; + position: absolute; + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + pointer-events: none; +} + +.image-cropper .bg_black { + background: rgba(0, 0, 0, 0.8) !important; +} + +.image-cropper .bg_gray { + background: rgba(0, 0, 0, 0.45); + transition-duration: .35s; +} + +.image-cropper .content>.content_top { + pointer-events: none; +} + +.image-cropper .content>.content_middle { + display: flex; + height: 200px; + width: 100%; +} + +.image-cropper .content_middle_middle { + width: 200px; + box-sizing: border-box; + position: relative; + transition-duration: .3s; +} + +.image-cropper .content_middle_right { + flex: auto; +} + +.image-cropper .content>.content_bottom { + flex: auto; +} + +.image-cropper .img { + z-index: 2; + top: 0; + left: 0; + position: absolute; + border: none; + width: 100%; + backface-visibility: hidden; + transform-origin: center; +} + +.image-cropper .image-cropper-canvas { + position: fixed; + background: white; + width: 150px; + height: 150px; + z-index: 10; + top: -200%; + pointer-events: none; +} + +.image-cropper .border { + background: white; + pointer-events: auto; + position: absolute; +} + +.image-cropper .border-top-left { + left: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-top-right { + right: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-right-top { + top: -1px; + width: 2.5px; + height: 30rpx; + right: -2.5px; +} + +.image-cropper .border-right-bottom { + width: 2.5px; + height: 30rpx; + right: -2.5px; + bottom: -1px; +} + +.image-cropper .border-bottom-left { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + left: -2.5px; +} + +.image-cropper .border-bottom-right { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + right: -2.5px; +} + +.image-cropper .border-left-top { + top: -1px; + width: 2.5px; + height: 30rpx; + left: -2.5px; +} + +.image-cropper .border-left-bottom { + width: 2.5px; + height: 30rpx; + left: -2.5px; + bottom: -1px; +} \ No newline at end of file diff --git a/demo/cropper/cropper.js b/demo/cropper/cropper.js new file mode 100644 index 0000000000000000000000000000000000000000..2e45db9a6a57a16cd9864296637e9d7089cdcf9d --- /dev/null +++ b/demo/cropper/cropper.js @@ -0,0 +1,183 @@ +//获取应用实例 +const app = getApp() +Page({ + data: { + src: '', + width: 250, //宽度 + height: 200, //高度 + max_width: 300, + max_height: 300, + disable_rotate: true, //是否禁用旋转 + disable_ratio: false, //锁定比例 + limit_move: true, //是否限制移动 + }, + onLoad: function (options) { + this.cropper = this.selectComponent("#image-cropper"); + this.setData({ + src: options.imgSrc + }); + this.cropper.upload(); //上传图片 + }, + cropperload(e) { + console.log('cropper加载完成'); + }, + loadimage(e) { + wx.hideLoading(); + console.log('图片'); + this.cropper.imgReset(); + }, + clickcut(e) { + console.log(e.detail); + //图片预览 + wx.previewImage({ + current: e.detail.url, // 当前显示图片的http链接 + urls: [e.detail.url] // 需要预览的图片http链接列表 + }) + }, + upload() { + let that = this; + wx.chooseImage({ + count: 1, + sizeType: ['original', 'compressed'], + sourceType: ['album', 'camera'], + success(res) { + wx.showLoading({ + title: '加载中', + }) + const tempFilePaths = res.tempFilePaths[0]; + //重置图片角度、缩放、位置 + that.cropper.imgReset(); + that.setData({ + src: tempFilePaths + }); + } + }) + }, + setWidth(e) { + this.setData({ + width: e.detail.value < 10 ? 10 : e.detail.value + }); + this.setData({ + cut_left: this.cropper.data.cut_left + }); + }, + setHeight(e) { + this.setData({ + height: e.detail.value < 10 ? 10 : e.detail.value + }); + this.setData({ + cut_top: this.cropper.data.cut_top + }); + }, + switchChangeDisableRatio(e) { + //设置宽度之后使剪裁框居中 + this.setData({ + disable_ratio: e.detail.value + }); + }, + setCutTop(e) { + this.setData({ + cut_top: e.detail.value + }); + this.setData({ + cut_top: this.cropper.data.cut_top + }); + }, + setCutLeft(e) { + this.setData({ + cut_left: e.detail.value + }); + this.setData({ + cut_left: this.cropper.data.cut_left + }); + }, + switchChangeDisableRotate(e) { + //开启旋转的同时不限制移动 + if (!e.detail.value) { + this.setData({ + limit_move: false, + disable_rotate: e.detail.value + }); + } else { + this.setData({ + disable_rotate: e.detail.value + }); + } + }, + switchChangeLimitMove(e) { + //限制移动的同时锁定旋转 + if (e.detail.value) { + this.setData({ + disable_rotate: true + }); + } + this.cropper.setLimitMove(e.detail.value); + }, + switchChangeDisableWidth(e) { + this.setData({ + disable_width: e.detail.value + }); + }, + switchChangeDisableHeight(e) { + this.setData({ + disable_height: e.detail.value + }); + }, + submit() { + this.cropper.getImg((obj) => { + app.globalData.imgSrc = obj.url; + wx.navigateBack({ + delta: -1 + }) + }); + }, + rotate() { + //在用户旋转的基础上旋转90° + this.cropper.setAngle(this.cropper.data.angle += 90); + }, + top() { + this.data.top = setInterval(() => { + this.cropper.setTransform({ + y: -3 + }); + }, 1000 / 60) + }, + bottom() { + this.data.bottom = setInterval(() => { + this.cropper.setTransform({ + y: 3 + }); + }, 1000 / 60) + }, + left() { + this.data.left = setInterval(() => { + this.cropper.setTransform({ + x: -3 + }); + }, 1000 / 60) + }, + right() { + this.data.right = setInterval(() => { + this.cropper.setTransform({ + x: 3 + }); + }, 1000 / 60) + }, + narrow() { + this.data.narrow = setInterval(() => { + this.cropper.setTransform({ + scale: -0.02 + }); + }, 1000 / 60) + }, + enlarge() { + this.data.enlarge = setInterval(() => { + this.cropper.setTransform({ + scale: 0.02 + }); + }, 1000 / 60) + }, + end(e) { + clearInterval(this.data[e.currentTarget.dataset.type]); + }, +}) \ No newline at end of file diff --git a/demo/cropper/cropper.json b/demo/cropper/cropper.json new file mode 100644 index 0000000000000000000000000000000000000000..ffa73b7db795348e47a510305a4646fcbb6d4103 --- /dev/null +++ b/demo/cropper/cropper.json @@ -0,0 +1,10 @@ +{ + "navigationBarTitleText": "image-cropper", + "disableScroll": true, + "navigationBarBackgroundColor": "#292929", + "navigationBarTextStyle": "white", + "backgroundColor": "#292929", + "usingComponents": { + "image-cropper": "../component/image-cropper/image-cropper" + } +} \ No newline at end of file diff --git a/demo/cropper/cropper.wxml b/demo/cropper/cropper.wxml new file mode 100644 index 0000000000000000000000000000000000000000..b86834297376776defc4122539b8327a3c525e98 --- /dev/null +++ b/demo/cropper/cropper.wxml @@ -0,0 +1,27 @@ + + + + +点击中间裁剪框可查看裁剪后的图片 + + 锁定裁剪框宽 + + 锁定裁剪框高 + + + 锁定比例 + + + 锁定旋转 + + 限制移动 + + + + + + + + + + \ No newline at end of file diff --git a/demo/cropper/cropper.wxss b/demo/cropper/cropper.wxss new file mode 100644 index 0000000000000000000000000000000000000000..1421bfa877c41d6040415b68e63ffa32b326d597 --- /dev/null +++ b/demo/cropper/cropper.wxss @@ -0,0 +1,218 @@ +.image-cropper { + background: rgba(14, 13, 13, .8); + position: fixed; + + .top { + position: absolute; + width: 100%; + top: 10rpx; + display: flex; + flex-flow: wrap; + z-index: 10; + color: white; + justify-content: space-around; + } + + .hint { + position: absolute; + top: 10rpx; + width: 100%; + font-size: 33rpx; + text-align: center; + color: white; + z-index: 10; + } + + page { + background: white; + } + + view { + font-size: 30rpx; + } + + .bottom { + position: absolute; + width: 100%; + bottom: 50rpx; + display: flex; + z-index: 10; + justify-content: center; + align-items: center; + flex-wrap: wrap; + height: 210rpx; + } + + button { + font-size: 27rpx; + z-index: 2; + padding: 0 20rpx; + height: 60rpx; + min-width: 70rpx; + margin: 0 4rpx; + } + + .input { + display: flex; + height: 50rpx; + width: 50%; + } + + .input>.label { + min-width: 150rpx; + font-size: 30rpx; + height: 50rpx; + line-height: 50rpx; + } + + .input>input { + margin-left: 10rpx; + text-align: center; + max-width: 160rpx; + border: 1px solid rgb(255, 255, 255); + height: 50rpx; + line-height: 50rpx; + min-height: 50rpx; + box-sizing: border-box; + } + + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1; +} + +.image-cropper .main { + position: absolute; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +.image-cropper .content { + z-index: 9; + position: absolute; + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + pointer-events: none; +} + +.image-cropper .bg_black { + background: rgba(0, 0, 0, 0.8) !important; +} + +.image-cropper .bg_gray { + background: rgba(0, 0, 0, 0.45); + transition-duration: .35s; +} + +.image-cropper .content>.content_top { + pointer-events: none; +} + +.image-cropper .content>.content_middle { + display: flex; + height: 200px; + width: 100%; +} + +.image-cropper .content_middle_middle { + width: 200px; + box-sizing: border-box; + position: relative; + transition-duration: .3s; +} + +.image-cropper .content_middle_right { + flex: auto; +} + +.image-cropper .content>.content_bottom { + flex: auto; +} + +.image-cropper .img { + z-index: 2; + top: 0; + left: 0; + position: absolute; + border: none; + width: 100%; + backface-visibility: hidden; + transform-origin: center; +} + +.image-cropper .image-cropper-canvas { + position: fixed; + background: white; + width: 150px; + height: 150px; + z-index: 10; + top: -200%; + pointer-events: none; +} + +.image-cropper .border { + background: white; + pointer-events: auto; + position: absolute; +} + +.image-cropper .border-top-left { + left: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-top-right { + right: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-right-top { + top: -1px; + width: 2.5px; + height: 30rpx; + right: -2.5px; +} + +.image-cropper .border-right-bottom { + width: 2.5px; + height: 30rpx; + right: -2.5px; + bottom: -1px; +} + +.image-cropper .border-bottom-left { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + left: -2.5px; +} + +.image-cropper .border-bottom-right { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + right: -2.5px; +} + +.image-cropper .border-left-top { + top: -1px; + width: 2.5px; + height: 30rpx; + left: -2.5px; +} + +.image-cropper .border-left-bottom { + width: 2.5px; + height: 30rpx; + left: -2.5px; + bottom: -1px; +} \ No newline at end of file diff --git a/demo/index/index.js b/demo/index/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7c21264a2e9be5d3f2d12fa81569d0f9d30d811e --- /dev/null +++ b/demo/index/index.js @@ -0,0 +1,25 @@ +const app = getApp() + +Page({ + data: { + src: '' + }, + toCropper() { + let url = 'https://pubser-res.zhenai.com/activity/202007/30/21480199030108.png'; + url = ''; + wx.navigateTo({ + url: `/cropper/cropper?imgSrc=${url}` + }) + }, + onShow() { + if (app.globalData.imgSrc) { + this.setData({ + src: app.globalData.imgSrc + }) + } + }, + onLoad: function () { + console.log('代码片段是一种迷你、可分享的小程序或小游戏项目,可用于分享小程序和小游戏的开发经验、展示组件和 API 的使用、复现开发问题和 Bug 等。可点击以下链接查看代码片段的详细文档:') + console.log('https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/devtools.html') + }, +}) \ No newline at end of file diff --git a/demo/index/index.json b/demo/index/index.json new file mode 100644 index 0000000000000000000000000000000000000000..397534a6a393ff527a837c6e733da17adcc90bd9 --- /dev/null +++ b/demo/index/index.json @@ -0,0 +1,7 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "image-cropper", + "navigationBarBackgroundColor": "#292929", + "navigationBarTextStyle": "white", + "backgroundColor": "#292929" +} \ No newline at end of file diff --git a/demo/index/index.wxml b/demo/index/index.wxml new file mode 100644 index 0000000000000000000000000000000000000000..da532d9f7a57a6a7e525bcbfe1fc314da1733581 --- /dev/null +++ b/demo/index/index.wxml @@ -0,0 +1,10 @@ + + + + + + + 点击裁剪 + + + \ No newline at end of file diff --git a/demo/index/index.wxss b/demo/index/index.wxss new file mode 100644 index 0000000000000000000000000000000000000000..582b7ba2059ddbca82bfcb040cf7207a8a3e075d --- /dev/null +++ b/demo/index/index.wxss @@ -0,0 +1,24 @@ +.intro { + background: #292929; + height: 100%; + width: 100%; + text-align: center; + display: flex; + justify-content: center; + align-items: center; +} + +.head { + margin-top: -20%; + overflow: hidden; + border-radius: 100%; + width: 200rpx; + height: 200rpx; + border: 4rpx solid #fff5f7; + line-height: 200rpx; + color: #fff5f7; +} + +.head>image { + width: 100%; +} \ No newline at end of file diff --git a/demo/project.config.json b/demo/project.config.json new file mode 100644 index 0000000000000000000000000000000000000000..b1f4e3e31a0fb22c54124da9cb6c150e6e5357e1 --- /dev/null +++ b/demo/project.config.json @@ -0,0 +1,70 @@ +{ + "description": "项目配置文件", + "packOptions": { + "ignore": [] + }, + "setting": { + "urlCheck": true, + "scopeDataCheck": false, + "coverView": true, + "es6": true, + "postcss": true, + "compileHotReLoad": false, + "preloadBackgroundData": false, + "minified": true, + "autoAudits": false, + "newFeature": false, + "uglifyFileName": false, + "uploadWithSourceMap": true, + "useIsolateContext": true, + "nodeModules": false, + "enhance": false, + "useCompilerModule": true, + "userConfirmedUseCompilerModuleSwitch": false, + "useMultiFrameRuntime": true, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": true, + "packNpmManually": false, + "packNpmRelationList": [], + "minifyWXSS": true, + "bundle": false + }, + "compileType": "miniprogram", + "libVersion": "2.15.0", + "appid": "wxef81ad8a767198db", + "projectname": "image-cropper", + "debugOptions": { + "hidedInDevtools": [] + }, + "simulatorType": "wechat", + "simulatorPluginLibVersion": {}, + "condition": { + "search": { + "list": [] + }, + "conversation": { + "list": [] + }, + "plugin": { + "list": [] + }, + "game": { + "currentL": -1, + "list": [] + }, + "gamePlugin": { + "list": [] + }, + "miniprogram": { + "list": [ + { + "id": -1, + "name": "cropper/cropper", + "pathName": "cropper/cropper", + "scene": null + } + ] + } + } +} \ No newline at end of file diff --git a/demo/sitemap.json b/demo/sitemap.json new file mode 100644 index 0000000000000000000000000000000000000000..ca02add20b581be471b8d17f887b8e8337070546 --- /dev/null +++ b/demo/sitemap.json @@ -0,0 +1,7 @@ +{ + "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", + "rules": [{ + "action": "allow", + "page": "*" + }] +} \ No newline at end of file diff --git a/src/image-cropper.js b/src/image-cropper.js index 6957179eed6e0493977399c2c63d905c54ccdaa3..91f3b21844f4924ef76b74c43460dfc4480ee8e6 100644 --- a/src/image-cropper.js +++ b/src/image-cropper.js @@ -1,1120 +1,1117 @@ Component({ - properties: { - /** - * 图片路径 - */ - 'imgSrc': { - type: String - }, - /** - * 裁剪框高度 - */ - 'height': { - type: Number, - value: 200 - }, - /** - * 裁剪框宽度 - */ - 'width': { - type: Number, - value: 200 - }, - /** - * 裁剪框最小尺寸 - */ - 'min_width': { - type: Number, - value: 100 - }, - 'min_height': { - type: Number, - value: 100 - }, - /** - * 裁剪框最大尺寸 - */ - 'max_width': { - type: Number, - value: 300 - }, - 'max_height': { - type: Number, - value: 300 - }, - /** - * 裁剪框禁止拖动 - */ - 'disable_width': { - type: Boolean, - value: false - }, - 'disable_height': { - type: Boolean, - value: false - }, - /** - * 锁定裁剪框比例 - */ - 'disable_ratio':{ - type: Boolean, - value: false - }, - /** - * 生成的图片尺寸相对剪裁框的比例 - */ - 'export_scale': { - type: Number, - value: 3 - }, - /** - * 生成的图片质量0-1 - */ - 'quality': { - type: Number, - value: 1 - }, - 'cut_top': { - type: Number, - value: null - }, - 'cut_left': { - type: Number, - value: null - }, - /** - * canvas上边距(不设置默认不显示) - */ - 'canvas_top': { - type: Number, - value: null - }, - /** - * canvas左边距(不设置默认不显示) - */ - 'canvas_left': { - type: Number, - value: null - }, - /** - * 图片宽度 - */ - 'img_width': { - type: null, - value: null - }, - /** - * 图片高度 - */ - 'img_height': { - type: null, - value: null - }, - /** - * 图片缩放比 - */ - 'scale': { - type: Number, - value: 1 - }, - /** - * 图片旋转角度 - */ - 'angle': { - type: Number, - value: 0 - }, - /** - * 最小缩放比 - */ - 'min_scale': { - type: Number, - value: 0.5 - }, - /** - * 最大缩放比 - */ - 'max_scale': { - type: Number, - value: 2 - }, - /** - * 是否禁用旋转 - */ - 'disable_rotate': { - type: Boolean, - value: false - }, - /** - * 是否限制移动范围(剪裁框只能在图片内) - */ - 'limit_move':{ - type: Boolean, - value: false - } - }, - data: { - el: 'image-cropper', //暂时无用 - info: wx.getSystemInfoSync(), - MOVE_THROTTLE:null,//触摸移动节流settimeout - MOVE_THROTTLE_FLAG: true,//节流标识 - INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) - INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) - TIME_BG: null,//背景变暗延时函数 - TIME_CUT_CENTER:null, - _touch_img_relative: [{ - x: 0, - y: 0 - }], //鼠标和图片中心的相对位置 - _flag_cut_touch:false,//是否是拖动裁剪框 - _hypotenuse_length: 0, //双指触摸时斜边长度 - _flag_img_endtouch: false, //是否结束触摸 - _flag_bright: true, //背景是否亮 - _canvas_overflow:true,//canvas缩略图是否在屏幕外面 - _canvas_width:200, - _canvas_height:200, - origin_x: 0.5, //图片旋转中心 - origin_y: 0.5, //图片旋转中心 - _cut_animation: false,//是否开启图片和裁剪框过渡 - _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距 - _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距 - watch: { - //监听截取框宽高变化 - width(value, that) { - if (value < that.data.min_width){ - that.setData({ - width: that.data.min_width - }); - } - that._computeCutSize(); - }, - height(value, that) { - if (value < that.data.min_height) { - that.setData({ - height: that.data.min_height - }); - } - that._computeCutSize(); - }, - angle(value, that){ - //停止居中裁剪框,继续修改图片位置 - that._moveStop(); - if(that.data.limit_move){ - if (that.data.angle % 90) { - that.setData({ - angle: Math.round(that.data.angle / 90) * 90 - }); - return; - } - } - }, - _cut_animation(value, that){ - //开启过渡300毫秒之后自动关闭 - clearTimeout(that.data._cut_animation_time); - if (value){ - that.data._cut_animation_time = setTimeout(()=>{ - that.setData({ - _cut_animation:false - }); - },300) - } - }, - limit_move(value, that){ - if (value) { - if (that.data.angle%90){ - that.setData({ - angle: Math.round(that.data.angle / 90)*90 - }); - } - that._imgMarginDetectionScale(); - !that.data._canvas_overflow && that._draw(); - } - }, - canvas_top(value, that){ - that._canvasDetectionPosition(); - }, - canvas_left(value, that){ - that._canvasDetectionPosition(); - }, - imgSrc(value, that){ - that.pushImg(); - }, - cut_top(value, that) { - that._cutDetectionPosition(); - if (that.data.limit_move) { - !that.data._canvas_overflow && that._draw(); - } - }, - cut_left(value, that) { - that._cutDetectionPosition(); - if (that.data.limit_move) { - !that.data._canvas_overflow && that._draw(); - } - } - } - }, - attached() { - this.data.info = wx.getSystemInfoSync(); - //启用数据监听 - this._watcher(); - this.data.INIT_IMGWIDTH = this.data.img_width; - this.data.INIT_IMGHEIGHT = this.data.img_height; - this.setData({ - _canvas_height: this.data.height, - _canvas_width: this.data.width, - }); - this._initCanvas(); - this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc); - //根据开发者设置的图片目标尺寸计算实际尺寸 - this._initImageSize(); - //设置裁剪框大小>设置图片尺寸>绘制canvas - this._computeCutSize(); - //检查裁剪框是否在范围内 - this._cutDetectionPosition(); - //检查canvas是否在范围内 - this._canvasDetectionPosition(); - //初始化完成 - this.triggerEvent('load', { - cropper: this - }); - }, - methods: { - /** - * 上传图片 - */ - upload() { - let that = this; - wx.chooseImage({ - count: 1, - sizeType: ['original', 'compressed'], - sourceType: ['album', 'camera'], - success(res) { - const tempFilePaths = res.tempFilePaths[0]; - that.pushImg(tempFilePaths); - wx.showLoading({ - title: '加载中...' - }) - } - }) - }, - /** - * 返回图片信息 - */ - getImg(getCallback) { - this._draw(()=>{ - wx.canvasToTempFilePath({ - width: this.data.width * this.data.export_scale, - height: Math.round(this.data.height * this.data.export_scale), - destWidth: this.data.width * this.data.export_scale, - destHeight: Math.round(this.data.height) * this.data.export_scale, - fileType: 'png', - quality: this.data.quality, - canvasId: this.data.el, - success: (res) => { - getCallback({ - url: res.tempFilePath, - width: this.data.width * this.data.export_scale, - height: this.data.height * this.data.export_scale - }); - } - }, this) - }); - }, - /** - * 设置图片动画 - * { - * x:10,//图片在原有基础上向下移动10px - * y:10,//图片在原有基础上向右移动10px - * angle:10,//图片在原有基础上旋转10deg - * scale:0.5,//图片在原有基础上增加0.5倍 - * } - */ - setTransform(transform) { - if (!transform) return; - if (!this.data.disable_rotate){ - this.setData({ - angle: transform.angle ? this.data.angle + transform.angle : this.data.angle - }); - } - var scale = this.data.scale; - if (transform.scale) { - scale = this.data.scale + transform.scale; - scale = scale <= this.data.min_scale ? this.data.min_scale : scale; - scale = scale >= this.data.max_scale ? this.data.max_scale : scale; - } - this.data.scale = scale; - let cutX = this.data.cut_left; - let cutY = this.data.cut_top; - if (transform.cutX){ - this.setData({ - cut_left: cutX + transform.cutX - }); - this.data.watch.cut_left(null, this); - } - if (transform.cutY){ - this.setData({ - cut_top: cutY + transform.cutY - }); - this.data.watch.cut_top(null, this); - } - this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top; - this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left; - //图像边缘检测,防止截取到空白 - this._imgMarginDetectionScale(); - //停止居中裁剪框,继续修改图片位置 - this._moveDuring(); - this.setData({ - scale: this.data.scale, - _img_top: this.data._img_top, - _img_left: this.data._img_left - }); - !this.data._canvas_overflow && this._draw(); - //可以居中裁剪框了 - this._moveStop();//结束操作 - }, - /** - * 设置剪裁框位置 - */ - setCutXY(x,y){ - this.setData({ - cut_top: y, - cut_left:x - }); - }, - /** - * 设置剪裁框尺寸 - */ - setCutSize(w,h){ - this.setData({ - width: w, - height:h - }); - this._computeCutSize(); - }, - /** - * 设置剪裁框和图片居中 - */ - setCutCenter() { - let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; - let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; - //顺序不能变 - this.setData({ - _img_top: this.data._img_top - this.data.cut_top + cut_top, - cut_top: cut_top, //截取的框上边距 - _img_left: this.data._img_left - this.data.cut_left + cut_left, - cut_left: cut_left, //截取的框左边距 - }); - }, - _setCutCenter(){ - let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; - let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; - this.setData({ - cut_top: cut_top, //截取的框上边距 - cut_left: cut_left, //截取的框左边距 - }); - }, - /** - * 设置剪裁框宽度-即将废弃 - */ - setWidth(width) { - this.setData({ - width: width - }); - this._computeCutSize(); - }, - /** - * 设置剪裁框高度-即将废弃 - */ - setHeight(height) { - this.setData({ - height: height - }); - this._computeCutSize(); - }, - /** - * 是否锁定旋转 - */ - setDisableRotate(value){ - this.data.disable_rotate = value; - }, - /** - * 是否限制移动 - */ - setLimitMove(value){ - this.setData({ - _cut_animation: true, - limit_move: !!value - }); - }, - /** - * 初始化图片,包括位置、大小、旋转角度 - */ - imgReset() { - this.setData({ - scale: 1, - angle: 0, - _img_top: wx.getSystemInfoSync().windowHeight / 2, - _img_left: wx.getSystemInfoSync().windowWidth / 2, - }) - }, - /** - * 加载(更换)图片 - */ - pushImg(src) { - if (src) { - this.setData({ - imgSrc: src - }); - //发现是手动赋值直接返回,交给watch处理 - return; - } - - // getImageInfo接口传入 src: '' 会导致内存泄漏 - - if (!this.data.imgSrc) return; - wx.getImageInfo({ - src: this.data.imgSrc, - success: (res) => { - this.data.imageObject = res; - //图片非本地路径需要换成本地路径 - if (this.data.imgSrc.search(/tmp/) == -1){ - this.setData({ - imgSrc: res.path - }); - } - //计算最后图片尺寸 - this._imgComputeSize(); - if (this.data.limit_move) { - //限制移动,不留空白处理 - this._imgMarginDetectionScale(); - } - this._draw(); + properties: { + /** + * 图片路径 + */ + 'imgSrc': { + type: String }, - fail: (err) => { - this.setData({ - imgSrc: '' - }); - } - }); - }, - imageLoad(e){ - setTimeout(()=>{ - this.triggerEvent('imageload', this.data.imageObject); - - },1000) - }, - /** - * 设置图片放大缩小 - */ - setScale(scale) { - if (!scale) return; - this.setData({ - scale: scale - }); - !this.data._canvas_overflow && this._draw(); - }, - /** - * 设置图片旋转角度 - */ - setAngle(angle) { - if (!angle) return; - this.setData({ - _cut_animation: true, - angle: angle - }); - this._imgMarginDetectionScale(); - !this.data._canvas_overflow && this._draw(); - }, - _initCanvas() { - //初始化canvas - if (!this.data.ctx){ - this.data.ctx = wx.createCanvasContext("image-cropper", this); - } - }, - /** - * 根据开发者设置的图片目标尺寸计算实际尺寸 - */ - _initImageSize(){ - //处理宽高特殊单位 %>px - if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) { - let width = this.data.INIT_IMGWIDTH.replace("%", ""); - this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width; - } - if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) { - let height = this.data.img_height.replace("%", ""); - this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height; - } - }, - /** - * 检测剪裁框位置是否在允许的范围内(屏幕内) - */ - _cutDetectionPosition(){ - let _cutDetectionPositionTop = () => { - //检测上边距是否在范围内 - if (this.data.cut_top < 0) { - this.setData({ - cut_top: 0 - }); - } - if (this.data.cut_top > this.data.info.windowHeight - this.data.height) { - this.setData({ - cut_top: this.data.info.windowHeight - this.data.height - }); - } - }, _cutDetectionPositionLeft = () => { - //检测左边距是否在范围内 - if (this.data.cut_left < 0) { - this.setData({ - cut_left: 0 - }); - } - if (this.data.cut_left > this.data.info.windowWidth - this.data.width) { - this.setData({ - cut_left: this.data.info.windowWidth - this.data.width - }); - } - }; - //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中) - if (this.data.cut_top == null && this.data.cut_left == null) { - this._setCutCenter(); - } else if (this.data.cut_top != null && this.data.cut_left != null){ - _cutDetectionPositionTop(); - _cutDetectionPositionLeft(); - } else if (this.data.cut_top != null && this.data.cut_left == null) { - _cutDetectionPositionTop(); - this.setData({ - cut_left: (this.data.info.windowWidth - this.data.width) / 2 - }); - } else if (this.data.cut_top == null && this.data.cut_left != null) { - _cutDetectionPositionLeft(); - this.setData({ - cut_top: (this.data.info.windowHeight - this.data.height) / 2 - }); - } - }, - /** - * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染 - * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外 - */ - _canvasDetectionPosition(){ - if(this.data.canvas_top == null && this.data.canvas_left == null) { - this.data._canvas_overflow = false; - this.setData({ - canvas_top: -5000, - canvas_left: -5000 - }); - }else if(this.data.canvas_top != null && this.data.canvas_left != null) { - if (this.data.canvas_top < - this.data.height || this.data.canvas_top > this.data.info.windowHeight) { - this.data._canvas_overflow = true; - } else { - this.data._canvas_overflow = false; - } - }else if(this.data.canvas_top != null && this.data.canvas_left == null) { - this.setData({ - canvas_left: 0 - }); - } else if (this.data.canvas_top == null && this.data.canvas_left != null) { - this.setData({ - canvas_top: 0 - }); - if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) { - this.data._canvas_overflow = true; - } else { - this.data._canvas_overflow = false; - } - } - }, - /** - * 图片边缘检测-位置 - */ - _imgMarginDetectionPosition(scale) { - if (!this.data.limit_move) return; - let left = this.data._img_left; - let top = this.data._img_top; - var scale = scale || this.data.scale; - let img_width = this.data.img_width; - let img_height = this.data.img_height; - if (this.data.angle / 90 % 2) { - img_width = this.data.img_height; - img_height = this.data.img_width; - } - left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2; - left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2; - top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2; - top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2; - this.setData({ - _img_left: left, - _img_top: top, - scale: scale - }) - }, - /** - * 图片边缘检测-缩放 - */ - _imgMarginDetectionScale(){ - if (!this.data.limit_move)return; - let scale = this.data.scale; - let img_width = this.data.img_width; - let img_height = this.data.img_height; - if (this.data.angle / 90 % 2) { - img_width = this.data.img_height; - img_height = this.data.img_width; - } - if (img_width * scale < this.data.width){ - scale = this.data.width / img_width; - } - if (img_height * scale < this.data.height) { - scale = Math.max(scale,this.data.height / img_height); - } - this._imgMarginDetectionPosition(scale); - }, - _setData(obj) { - let data = {}; - for (var key in obj) { - if (this.data[key] != obj[key]){ - data[key] = obj[key]; + /** + * 裁剪框高度 + */ + 'height': { + type: Number, + value: 200 + }, + /** + * 裁剪框宽度 + */ + 'width': { + type: Number, + value: 200 + }, + /** + * 裁剪框最小尺寸 + */ + 'min_width': { + type: Number, + value: 100 + }, + 'min_height': { + type: Number, + value: 100 + }, + /** + * 裁剪框最大尺寸 + */ + 'max_width': { + type: Number, + value: 300 + }, + 'max_height': { + type: Number, + value: 300 + }, + /** + * 裁剪框禁止拖动 + */ + 'disable_width': { + type: Boolean, + value: false + }, + 'disable_height': { + type: Boolean, + value: false + }, + /** + * 锁定裁剪框比例 + */ + 'disable_ratio': { + type: Boolean, + value: false + }, + /** + * 生成的图片尺寸相对剪裁框的比例 + */ + 'export_scale': { + type: Number, + value: 3 + }, + /** + * 生成的图片质量0-1 + */ + 'quality': { + type: Number, + value: 1 + }, + 'cut_top': { + type: Number, + value: null + }, + 'cut_left': { + type: Number, + value: null + }, + /** + * canvas上边距(不设置默认不显示) + */ + 'canvas_top': { + type: Number, + value: null + }, + /** + * canvas左边距(不设置默认不显示) + */ + 'canvas_left': { + type: Number, + value: null + }, + /** + * 图片宽度 + */ + 'img_width': { + type: null, + value: null + }, + /** + * 图片高度 + */ + 'img_height': { + type: null, + value: null + }, + /** + * 图片缩放比 + */ + 'scale': { + type: Number, + value: 1 + }, + /** + * 图片旋转角度 + */ + 'angle': { + type: Number, + value: 0 + }, + /** + * 最小缩放比 + */ + 'min_scale': { + type: Number, + value: 0.5 + }, + /** + * 最大缩放比 + */ + 'max_scale': { + type: Number, + value: 2 + }, + /** + * 是否禁用旋转 + */ + 'disable_rotate': { + type: Boolean, + value: false + }, + /** + * 是否限制移动范围(剪裁框只能在图片内) + */ + 'limit_move': { + type: Boolean, + value: false } - } - this.setData(data); - return data; }, - /** - * 计算图片尺寸 - */ - _imgComputeSize() { - let img_width = this.data.img_width, - img_height = this.data.img_height; - if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { - //默认按图片最小边 = 对应裁剪框尺寸 - img_width = this.data.imageObject.width; - img_height = this.data.imageObject.height; - if (img_width / img_height > this.data.width / this.data.height){ - img_height = this.data.height; - img_width = this.data.imageObject.width / this.data.imageObject.height * img_height; - }else{ - img_width = this.data.width; - img_height = this.data.imageObject.height / this.data.imageObject.width * img_width; + data: { + el: 'image-cropper', //暂时无用 + info: wx.getSystemInfoSync(), + MOVE_THROTTLE: null, //触摸移动节流settimeout + MOVE_THROTTLE_FLAG: true, //节流标识 + INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) + INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) + TIME_BG: null, //背景变暗延时函数 + TIME_CUT_CENTER: null, + _touch_img_relative: [{ + x: 0, + y: 0 + }], //鼠标和图片中心的相对位置 + _flag_cut_touch: false, //是否是拖动裁剪框 + _hypotenuse_length: 0, //双指触摸时斜边长度 + _flag_img_endtouch: false, //是否结束触摸 + _flag_bright: true, //背景是否亮 + _canvas_overflow: true, //canvas缩略图是否在屏幕外面 + _canvas_width: 200, + _canvas_height: 200, + origin_x: 0.5, //图片旋转中心 + origin_y: 0.5, //图片旋转中心 + _cut_animation: false, //是否开启图片和裁剪框过渡 + _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距 + _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距 + watch: { + //监听截取框宽高变化 + width(value, that) { + if (value < that.data.min_width) { + that.setData({ + width: that.data.min_width + }); + } + that._computeCutSize(); + }, + height(value, that) { + if (value < that.data.min_height) { + that.setData({ + height: that.data.min_height + }); + } + that._computeCutSize(); + }, + angle(value, that) { + //停止居中裁剪框,继续修改图片位置 + that._moveStop(); + if (that.data.limit_move) { + if (that.data.angle % 90) { + that.setData({ + angle: Math.round(that.data.angle / 90) * 90 + }); + return; + } + } + }, + _cut_animation(value, that) { + //开启过渡300毫秒之后自动关闭 + clearTimeout(that.data._cut_animation_time); + if (value) { + that.data._cut_animation_time = setTimeout(() => { + that.setData({ + _cut_animation: false + }); + }, 300) + } + }, + limit_move(value, that) { + if (value) { + if (that.data.angle % 90) { + that.setData({ + angle: Math.round(that.data.angle / 90) * 90 + }); + } + that._imgMarginDetectionScale(); + !that.data._canvas_overflow && that._draw(); + } + }, + canvas_top(value, that) { + that._canvasDetectionPosition(); + }, + canvas_left(value, that) { + that._canvasDetectionPosition(); + }, + imgSrc(value, that) { + that.pushImg(); + }, + cut_top(value, that) { + that._cutDetectionPosition(); + if (that.data.limit_move) { + !that.data._canvas_overflow && that._draw(); + } + }, + cut_left(value, that) { + that._cutDetectionPosition(); + if (that.data.limit_move) { + !that.data._canvas_overflow && that._draw(); + } + } } - } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { - img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT; - } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) { - img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH; - } - this.setData({ - img_width: img_width, - img_height: img_height - }); }, - //改变截取框大小 - _computeCutSize() { - if (this.data.width > this.data.info.windowWidth) { + attached() { + this.data.info = wx.getSystemInfoSync(); + //启用数据监听 + this._watcher(); + this.data.INIT_IMGWIDTH = this.data.img_width; + this.data.INIT_IMGHEIGHT = this.data.img_height; this.setData({ - width: this.data.info.windowWidth, + _canvas_height: this.data.height, + _canvas_width: this.data.width, }); - } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth){ - this.setData({ - cut_left: this.data.info.windowWidth - this.data.cut_left, - }); - }; - if (this.data.height > this.data.info.windowHeight) { - this.setData({ - height: this.data.info.windowHeight, + this._initCanvas(); + this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc); + //根据开发者设置的图片目标尺寸计算实际尺寸 + this._initImageSize(); + //设置裁剪框大小>设置图片尺寸>绘制canvas + this._computeCutSize(); + //检查裁剪框是否在范围内 + this._cutDetectionPosition(); + //检查canvas是否在范围内 + this._canvasDetectionPosition(); + //初始化完成 + this.triggerEvent('load', { + cropper: this }); - } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight){ - this.setData({ - cut_top: this.data.info.windowHeight - this.data.cut_top, - }); - } - !this.data._canvas_overflow && this._draw(); - }, - //开始触摸 - _start(event) { - this.data._flag_img_endtouch = false; - if (event.touches.length == 1) { - //单指拖动 - this.data._touch_img_relative[0] = { - x: (event.touches[0].clientX - this.data._img_left), - y: (event.touches[0].clientY - this.data._img_top) - } - } else { - //双指放大 - let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX); - let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY); - this.data._touch_img_relative = [{ - x: (event.touches[0].clientX - this.data._img_left), - y: (event.touches[0].clientY - this.data._img_top) - }, { - x: (event.touches[1].clientX - this.data._img_left), - y: (event.touches[1].clientY - this.data._img_top) - }]; - this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); - } - !this.data._canvas_overflow && this._draw(); }, - _move_throttle(){ - //安卓需要节流 - if (this.data.info.platform =='android'){ - clearTimeout(this.data.MOVE_THROTTLE); - this.data.MOVE_THROTTLE = setTimeout(() => { - this.data.MOVE_THROTTLE_FLAG = true; - }, 1000 / 40) - return this.data.MOVE_THROTTLE_FLAG; - }else{ - this.data.MOVE_THROTTLE_FLAG = true; - } - }, - _move(event) { - if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return; - this.data.MOVE_THROTTLE_FLAG = false; - this._move_throttle(); - this._moveDuring(); - if (event.touches.length == 1) { - //单指拖动 - let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x), - top = (event.touches[0].clientY - this.data._touch_img_relative[0].y); - //图像边缘检测,防止截取到空白 - this.data._img_left = left; - this.data._img_top = top; - this._imgMarginDetectionPosition(); - this.setData({ - _img_left: this.data._img_left, - _img_top: this.data._img_top - }); - } else { - //双指放大 - let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)), - height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)), - hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)), - scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length), - current_deg = 0; - scale = scale <= this.data.min_scale ? this.data.min_scale : scale; - scale = scale >= this.data.max_scale ? this.data.max_scale : scale; - //图像边缘检测,防止截取到空白 - this.data.scale = scale; - this._imgMarginDetectionScale(); - //双指旋转(如果没禁用旋转) - let _touch_img_relative = [{ - x: (event.touches[0].clientX - this.data._img_left), - y: (event.touches[0].clientY - this.data._img_top) - }, { - x: (event.touches[1].clientX - this.data._img_left), - y: (event.touches[1].clientY - this.data._img_top) - }]; - if (!this.data.disable_rotate){ - let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x); - let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x); - let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x); - let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x); - //当前旋转的角度 - let first_deg = first_atan - first_atan_old, - second_deg = second_atan - second_atan_old; - if (first_deg != 0) { - current_deg = first_deg; - } else if (second_deg != 0) { - current_deg = second_deg; - } - } - this.data._touch_img_relative = _touch_img_relative; - this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); - //更新视图 - this.setData({ - angle: this.data.angle + current_deg, - scale: this.data.scale - }); - } - !this.data._canvas_overflow && this._draw(); - }, - //结束操作 - _end(event) { - this.data._flag_img_endtouch = true; - this._moveStop(); - }, - //点击中间剪裁框处理 - _click(event) { - if (!this.data.imgSrc) { - //调起上传 - this.upload(); - return; - } - this._draw(()=>{ - let x = event.detail ? event.detail.x : event.touches[0].clientX; - let y = event.detail ? event.detail.y : event.touches[0].clientY; - if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) { - //生成图片并回调 - wx.canvasToTempFilePath({ - width: this.data.width * this.data.export_scale, - height: Math.round(this.data.height * this.data.export_scale), - destWidth: this.data.width * this.data.export_scale, - destHeight: Math.round(this.data.height) * this.data.export_scale, - fileType: 'png', - quality: this.data.quality, - canvasId: this.data.el, - success: (res) => { - this.triggerEvent('tapcut', { - url: res.tempFilePath, - width: this.data.width * this.data.export_scale, - height: this.data.height * this.data.export_scale - }); - } - }, this) - } - }); - }, - //渲染 - _draw(callback) { - if (!this.data.imgSrc) return; - let draw = () => { - //图片实际大小 - let img_width = this.data.img_width * this.data.scale * this.data.export_scale; - let img_height = this.data.img_height * this.data.scale * this.data.export_scale; - //canvas和图片的相对距离 - var xpos = this.data._img_left - this.data.cut_left; - var ypos = this.data._img_top - this.data.cut_top; - //旋转画布 - this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale); - this.data.ctx.rotate(this.data.angle * Math.PI / 180); - this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height); - this.data.ctx.draw(false, () => { - callback && callback(); - }); - } - if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height){ - //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方 - this.setData({ - _canvas_height: this.data.height, - _canvas_width: this.data.width, - },()=>{ - //延迟40毫秒防止点击过快出现拉伸或裁剪过多 - setTimeout(() => { - draw(); - }, 40); - }); - }else{ - draw(); - } - }, - //裁剪框处理 - _cutTouchMove(e) { - if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) { - if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return; - //节流 - this.data.MOVE_THROTTLE_FLAG = false; - this._move_throttle(); - let width = this.data.width, - height = this.data.height, - cut_top = this.data.cut_top, - cut_left = this.data.cut_left, - size_correct = () => { - width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width; - height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height; - }, - size_inspect = () => { - if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) { - size_correct(); - return false; - } else { - size_correct(); - return true; - } - }; - height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY)); - switch (this.data.CUT_START.corner) { - case 1: - width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; - if (this.data.disable_ratio) { - height = width / (this.data.width / this.data.height) - } - if (!size_inspect()) return; - cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width); - break - case 2: - width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; - if (this.data.disable_ratio) { - height = width / (this.data.width / this.data.height) - } - if (!size_inspect()) return; - cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height) - cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width) - break - case 3: - width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; - if (this.data.disable_ratio) { - height = width / (this.data.width / this.data.height) - } - if (!size_inspect()) return; - cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height); - break - case 4: - width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; - if (this.data.disable_ratio) { - height = width / (this.data.width / this.data.height) - } - if (!size_inspect()) return; - break - } - if (!this.data.disable_width && !this.data.disable_height) { - this.setData({ - width: width, - cut_left: cut_left, - height: height, - cut_top: cut_top, - }) - } else if (!this.data.disable_width) { - this.setData({ - width: width, - cut_left: cut_left - }) - } else if (!this.data.disable_height) { - this.setData({ - height: height, - cut_top: cut_top - }) - } - this._imgMarginDetectionScale(); - } - }, - _cutTouchStart(e) { - let currentX = e.touches[0].clientX; - let currentY = e.touches[0].clientY; - let cutbox_top4 = this.data.cut_top + this.data.height - 30; - let cutbox_bottom4 = this.data.cut_top + this.data.height + 20; - let cutbox_left4 = this.data.cut_left + this.data.width - 30; - let cutbox_right4 = this.data.cut_left + this.data.width + 30; + methods: { + /** + * 上传图片 + */ + upload() { + let that = this; + wx.chooseImage({ + count: 1, + sizeType: ['original', 'compressed'], + sourceType: ['album', 'camera'], + success(res) { + const tempFilePaths = res.tempFilePaths[0]; + that.pushImg(tempFilePaths); + wx.showLoading({ + title: '加载中...' + }) + } + }) + }, + /** + * 返回图片信息 + */ + getImg(getCallback) { + this._draw(() => { + wx.canvasToTempFilePath({ + width: this.data.width * this.data.export_scale, + height: Math.round(this.data.height * this.data.export_scale), + destWidth: this.data.width * this.data.export_scale, + destHeight: Math.round(this.data.height) * this.data.export_scale, + fileType: 'png', + quality: this.data.quality, + canvasId: this.data.el, + success: (res) => { + getCallback({ + url: res.tempFilePath, + width: this.data.width * this.data.export_scale, + height: this.data.height * this.data.export_scale + }); + } + }, this) + }); + }, + /** + * 设置图片动画 + * { + * x:10,//图片在原有基础上向下移动10px + * y:10,//图片在原有基础上向右移动10px + * angle:10,//图片在原有基础上旋转10deg + * scale:0.5,//图片在原有基础上增加0.5倍 + * } + */ + setTransform(transform) { + if (!transform) return; + if (!this.data.disable_rotate) { + this.setData({ + angle: transform.angle ? this.data.angle + transform.angle : this.data.angle + }); + } + var scale = this.data.scale; + if (transform.scale) { + scale = this.data.scale + transform.scale; + scale = scale <= this.data.min_scale ? this.data.min_scale : scale; + scale = scale >= this.data.max_scale ? this.data.max_scale : scale; + } + this.data.scale = scale; + let cutX = this.data.cut_left; + let cutY = this.data.cut_top; + if (transform.cutX) { + this.setData({ + cut_left: cutX + transform.cutX + }); + this.data.watch.cut_left(null, this); + } + if (transform.cutY) { + this.setData({ + cut_top: cutY + transform.cutY + }); + this.data.watch.cut_top(null, this); + } + this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top; + this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left; + //图像边缘检测,防止截取到空白 + this._imgMarginDetectionScale(); + //停止居中裁剪框,继续修改图片位置 + this._moveDuring(); + this.setData({ + scale: this.data.scale, + _img_top: this.data._img_top, + _img_left: this.data._img_left + }); + !this.data._canvas_overflow && this._draw(); + //可以居中裁剪框了 + this._moveStop(); //结束操作 + }, + /** + * 设置剪裁框位置 + */ + setCutXY(x, y) { + this.setData({ + cut_top: y, + cut_left: x + }); + }, + /** + * 设置剪裁框尺寸 + */ + setCutSize(w, h) { + this.setData({ + width: w, + height: h + }); + this._computeCutSize(); + }, + /** + * 设置剪裁框和图片居中 + */ + setCutCenter() { + let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; + let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; + //顺序不能变 + this.setData({ + _img_top: this.data._img_top - this.data.cut_top + cut_top, + cut_top: cut_top, //截取的框上边距 + _img_left: this.data._img_left - this.data.cut_left + cut_left, + cut_left: cut_left, //截取的框左边距 + }); + }, + _setCutCenter() { + let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; + let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; + this.setData({ + cut_top: cut_top, //截取的框上边距 + cut_left: cut_left, //截取的框左边距 + }); + }, + /** + * 设置剪裁框宽度-即将废弃 + */ + setWidth(width) { + this.setData({ + width: width + }); + this._computeCutSize(); + }, + /** + * 设置剪裁框高度-即将废弃 + */ + setHeight(height) { + this.setData({ + height: height + }); + this._computeCutSize(); + }, + /** + * 是否锁定旋转 + */ + setDisableRotate(value) { + this.data.disable_rotate = value; + }, + /** + * 是否限制移动 + */ + setLimitMove(value) { + this.setData({ + _cut_animation: true, + limit_move: !!value + }); + }, + /** + * 初始化图片,包括位置、大小、旋转角度 + */ + imgReset() { + this.setData({ + scale: 1, + angle: 0, + _img_top: wx.getSystemInfoSync().windowHeight / 2, + _img_left: wx.getSystemInfoSync().windowWidth / 2, + }) + }, + /** + * 加载(更换)图片 + */ + pushImg(src) { + if (src) { + this.setData({ + imgSrc: src + }); + //发现是手动赋值直接返回,交给watch处理 + return; + } - let cutbox_top3 = this.data.cut_top - 30; - let cutbox_bottom3 = this.data.cut_top + 30; - let cutbox_left3 = this.data.cut_left + this.data.width - 30; - let cutbox_right3 = this.data.cut_left + this.data.width + 30; + // getImageInfo接口传入 src: '' 会导致内存泄漏 - let cutbox_top2 = this.data.cut_top - 30; - let cutbox_bottom2 = this.data.cut_top + 30; - let cutbox_left2 = this.data.cut_left - 30; - let cutbox_right2 = this.data.cut_left + 30; + if (!this.data.imgSrc) return; + wx.getImageInfo({ + src: this.data.imgSrc, + success: (res) => { + this.data.imageObject = res; + //图片非本地路径需要换成本地路径 + if (this.data.imgSrc.search(/tmp/) == -1) { + this.setData({ + imgSrc: res.path + }); + } + //计算最后图片尺寸 + this._imgComputeSize(); + if (this.data.limit_move) { + //限制移动,不留空白处理 + this._imgMarginDetectionScale(); + } + this._draw(); + }, + fail: (err) => { + this.setData({ + imgSrc: '' + }); + } + }); + }, + imageLoad(e) { + setTimeout(() => { + this.triggerEvent('imageload', this.data.imageObject); - let cutbox_top1 = this.data.cut_top + this.data.height - 30; - let cutbox_bottom1 = this.data.cut_top + this.data.height + 30; - let cutbox_left1 = this.data.cut_left - 30; - let cutbox_right1 = this.data.cut_left + 30; - if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) { - this._moveDuring(); - this.data._flag_cut_touch = true; - this.data._flag_img_endtouch = true; - this.data.CUT_START = { - width: this.data.width, - height: this.data.height, - x: currentX, - y: currentY, - corner: 4 - } - } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) { - this._moveDuring(); - this.data._flag_cut_touch = true; - this.data._flag_img_endtouch = true; - this.data.CUT_START = { - width: this.data.width, - height: this.data.height, - x: currentX, - y: currentY, - cut_top: this.data.cut_top, - cut_left: this.data.cut_left, - corner: 3 - } - } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) { - this._moveDuring(); - this.data._flag_cut_touch = true; - this.data._flag_img_endtouch = true; - this.data.CUT_START = { - width: this.data.width, - height: this.data.height, - cut_top: this.data.cut_top, - cut_left: this.data.cut_left, - x: currentX, - y: currentY, - corner: 2 - } - } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) { - this._moveDuring(); - this.data._flag_cut_touch = true; - this.data._flag_img_endtouch = true; - this.data.CUT_START = { - width: this.data.width, - height: this.data.height, - cut_top: this.data.cut_top, - cut_left: this.data.cut_left, - x: currentX, - y: currentY, - corner: 1 - } - } - }, - _cutTouchEnd(e) { - this._moveStop(); - this.data._flag_cut_touch = false; - }, - //停止移动时需要做的操作 - _moveStop() { - //清空之前的自动居中延迟函数并添加最新的 - clearTimeout(this.data.TIME_CUT_CENTER); - this.data.TIME_CUT_CENTER = setTimeout(() => { - //动画启动 - if (!this.data._cut_animation) { - this.setData({ - _cut_animation: true - }); - } - this.setCutCenter(); - }, 1000) - //清空之前的背景变化延迟函数并添加最新的 - clearTimeout(this.data.TIME_BG); - this.data.TIME_BG = setTimeout(() => { - if (this.data._flag_bright) { - this.setData({ - _flag_bright: false - }); - } - }, 2000) - }, - //移动中 - _moveDuring() { - //清空之前的自动居中延迟函数 - clearTimeout(this.data.TIME_CUT_CENTER); - //清空之前的背景变化延迟函数 - clearTimeout(this.data.TIME_BG); - //高亮背景 - if (!this.data._flag_bright) { - this.setData({ - _flag_bright: true - }); - } - }, - //监听器 - _watcher() { - Object.keys(this.data).forEach(v => { - this._observe(this.data, v, this.data.watch[v]); - }) - }, - _observe(obj, key, watchFun) { - var val = obj[key]; - Object.defineProperty(obj, key, { - configurable: true, - enumerable: true, - set:(value) => { - val = value; - watchFun && watchFun(val, this); - }, - get() { - if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key)!=-1){ - let ret = parseFloat(parseFloat(val).toFixed(3)); - if (typeof val == "string" && val.indexOf("%") != -1){ - ret+='%'; - } - return ret; - } - return val; - } - }) - }, - _preventTouchMove() { + }, 1000) + }, + /** + * 设置图片放大缩小 + */ + setScale(scale) { + if (!scale) return; + this.setData({ + scale: scale + }); + !this.data._canvas_overflow && this._draw(); + }, + /** + * 设置图片旋转角度 + */ + setAngle(angle) { + if (!angle) return; + this.setData({ + _cut_animation: true, + angle: angle + }); + this._imgMarginDetectionScale(); + !this.data._canvas_overflow && this._draw(); + }, + _initCanvas() { + //初始化canvas + if (!this.data.ctx) { + this.data.ctx = wx.createCanvasContext("image-cropper", this); + } + }, + /** + * 根据开发者设置的图片目标尺寸计算实际尺寸 + */ + _initImageSize() { + //处理宽高特殊单位 %>px + if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) { + let width = this.data.INIT_IMGWIDTH.replace("%", ""); + this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width; + } + if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) { + let height = this.data.img_height.replace("%", ""); + this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height; + } + }, + /** + * 检测剪裁框位置是否在允许的范围内(屏幕内) + */ + _cutDetectionPosition() { + let _cutDetectionPositionTop = () => { + //检测上边距是否在范围内 + if (this.data.cut_top < 0) { + this.setData({ + cut_top: 0 + }); + } + if (this.data.cut_top > this.data.info.windowHeight - this.data.height) { + this.setData({ + cut_top: this.data.info.windowHeight - this.data.height + }); + } + }, + _cutDetectionPositionLeft = () => { + //检测左边距是否在范围内 + if (this.data.cut_left < 0) { + this.setData({ + cut_left: 0 + }); + } + if (this.data.cut_left > this.data.info.windowWidth - this.data.width) { + this.setData({ + cut_left: this.data.info.windowWidth - this.data.width + }); + } + }; + //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中) + if (this.data.cut_top == null && this.data.cut_left == null) { + this._setCutCenter(); + } else if (this.data.cut_top != null && this.data.cut_left != null) { + _cutDetectionPositionTop(); + _cutDetectionPositionLeft(); + } else if (this.data.cut_top != null && this.data.cut_left == null) { + _cutDetectionPositionTop(); + this.setData({ + cut_left: (this.data.info.windowWidth - this.data.width) / 2 + }); + } else if (this.data.cut_top == null && this.data.cut_left != null) { + _cutDetectionPositionLeft(); + this.setData({ + cut_top: (this.data.info.windowHeight - this.data.height) / 2 + }); + } + }, + /** + * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染 + * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外 + */ + _canvasDetectionPosition() { + if (this.data.canvas_top == null && this.data.canvas_left == null) { + this.data._canvas_overflow = false; + this.setData({ + canvas_top: -5000, + canvas_left: -5000 + }); + } else if (this.data.canvas_top != null && this.data.canvas_left != null) { + if (this.data.canvas_top < -this.data.height || this.data.canvas_top > this.data.info.windowHeight) { + this.data._canvas_overflow = true; + } else { + this.data._canvas_overflow = false; + } + } else if (this.data.canvas_top != null && this.data.canvas_left == null) { + this.setData({ + canvas_left: 0 + }); + } else if (this.data.canvas_top == null && this.data.canvas_left != null) { + this.setData({ + canvas_top: 0 + }); + if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) { + this.data._canvas_overflow = true; + } else { + this.data._canvas_overflow = false; + } + } + }, + /** + * 图片边缘检测-位置 + */ + _imgMarginDetectionPosition(scale) { + if (!this.data.limit_move) return; + let left = this.data._img_left; + let top = this.data._img_top; + var scale = scale || this.data.scale; + let img_width = this.data.img_width; + let img_height = this.data.img_height; + if (this.data.angle / 90 % 2) { + img_width = this.data.img_height; + img_height = this.data.img_width; + } + left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2; + left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2; + top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2; + top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2; + this.setData({ + _img_left: left, + _img_top: top, + scale: scale + }) + }, + /** + * 图片边缘检测-缩放 + */ + _imgMarginDetectionScale() { + if (!this.data.limit_move) return; + let scale = this.data.scale; + let img_width = this.data.img_width; + let img_height = this.data.img_height; + if (this.data.angle / 90 % 2) { + img_width = this.data.img_height; + img_height = this.data.img_width; + } + if (img_width * scale < this.data.width) { + scale = this.data.width / img_width; + } + if (img_height * scale < this.data.height) { + scale = Math.max(scale, this.data.height / img_height); + } + this._imgMarginDetectionPosition(scale); + }, + _setData(obj) { + let data = {}; + for (var key in obj) { + if (this.data[key] != obj[key]) { + data[key] = obj[key]; + } + } + this.setData(data); + return data; + }, + /** + * 计算图片尺寸 + */ + _imgComputeSize() { + let img_width = this.data.img_width, + img_height = this.data.img_height; + if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { + //默认按图片最小边 = 对应裁剪框尺寸 + img_width = this.data.imageObject.width; + img_height = this.data.imageObject.height; + if (img_width / img_height > this.data.width / this.data.height) { + img_height = this.data.height; + img_width = this.data.imageObject.width / this.data.imageObject.height * img_height; + } else { + img_width = this.data.width; + img_height = this.data.imageObject.height / this.data.imageObject.width * img_width; + } + } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { + img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT; + } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) { + img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH; + } + this.setData({ + img_width: img_width, + img_height: img_height + }); + }, + //改变截取框大小 + _computeCutSize() { + if (this.data.width > this.data.info.windowWidth) { + this.setData({ + width: this.data.info.windowWidth, + }); + } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) { + this.setData({ + cut_left: this.data.info.windowWidth - this.data.cut_left, + }); + }; + if (this.data.height > this.data.info.windowHeight) { + this.setData({ + height: this.data.info.windowHeight, + }); + } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) { + this.setData({ + cut_top: this.data.info.windowHeight - this.data.cut_top, + }); + }!this.data._canvas_overflow && this._draw(); + }, + //开始触摸 + _start(event) { + this.data._flag_img_endtouch = false; + if (event.touches.length == 1) { + //单指拖动 + this.data._touch_img_relative[0] = { + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + } + } else { + //双指放大 + let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX); + let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY); + this.data._touch_img_relative = [{ + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + }, { + x: (event.touches[1].clientX - this.data._img_left), + y: (event.touches[1].clientY - this.data._img_top) + }]; + this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + }!this.data._canvas_overflow && this._draw(); + }, + _move_throttle() { + //安卓需要节流 + if (this.data.info.platform == 'android') { + clearTimeout(this.data.MOVE_THROTTLE); + this.data.MOVE_THROTTLE = setTimeout(() => { + this.data.MOVE_THROTTLE_FLAG = true; + }, 1000 / 40) + return this.data.MOVE_THROTTLE_FLAG; + } else { + this.data.MOVE_THROTTLE_FLAG = true; + } + }, + _move(event) { + if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return; + this.data.MOVE_THROTTLE_FLAG = false; + this._move_throttle(); + this._moveDuring(); + if (event.touches.length == 1) { + //单指拖动 + let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x), + top = (event.touches[0].clientY - this.data._touch_img_relative[0].y); + //图像边缘检测,防止截取到空白 + this.data._img_left = left; + this.data._img_top = top; + this._imgMarginDetectionPosition(); + this.setData({ + _img_left: this.data._img_left, + _img_top: this.data._img_top + }); + } else { + //双指放大 + let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)), + height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)), + hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)), + scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length), + current_deg = 0; + scale = scale <= this.data.min_scale ? this.data.min_scale : scale; + scale = scale >= this.data.max_scale ? this.data.max_scale : scale; + //图像边缘检测,防止截取到空白 + this.data.scale = scale; + this._imgMarginDetectionScale(); + //双指旋转(如果没禁用旋转) + let _touch_img_relative = [{ + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + }, { + x: (event.touches[1].clientX - this.data._img_left), + y: (event.touches[1].clientY - this.data._img_top) + }]; + if (!this.data.disable_rotate) { + let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x); + let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x); + let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x); + let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x); + //当前旋转的角度 + let first_deg = first_atan - first_atan_old, + second_deg = second_atan - second_atan_old; + if (first_deg != 0) { + current_deg = first_deg; + } else if (second_deg != 0) { + current_deg = second_deg; + } + } + this.data._touch_img_relative = _touch_img_relative; + this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + //更新视图 + this.setData({ + angle: this.data.angle + current_deg, + scale: this.data.scale + }); + }!this.data._canvas_overflow && this._draw(); + }, + //结束操作 + _end(event) { + this.data._flag_img_endtouch = true; + this._moveStop(); + }, + //点击中间剪裁框处理 + _click(event) { + if (!this.data.imgSrc) { + //调起上传 + this.upload(); + return; + } + this._draw(() => { + let x = event.detail ? event.detail.x : event.touches[0].clientX; + let y = event.detail ? event.detail.y : event.touches[0].clientY; + if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) { + //生成图片并回调 + wx.canvasToTempFilePath({ + width: this.data.width * this.data.export_scale, + height: Math.round(this.data.height * this.data.export_scale), + destWidth: this.data.width * this.data.export_scale, + destHeight: Math.round(this.data.height) * this.data.export_scale, + fileType: 'png', + quality: this.data.quality, + canvasId: this.data.el, + success: (res) => { + this.triggerEvent('tapcut', { + url: res.tempFilePath, + width: this.data.width * this.data.export_scale, + height: this.data.height * this.data.export_scale + }); + } + }, this) + } + }); + }, + //渲染 + _draw(callback) { + if (!this.data.imgSrc) return; + let draw = () => { + //图片实际大小 + let img_width = this.data.img_width * this.data.scale * this.data.export_scale; + let img_height = this.data.img_height * this.data.scale * this.data.export_scale; + //canvas和图片的相对距离 + var xpos = this.data._img_left - this.data.cut_left; + var ypos = this.data._img_top - this.data.cut_top; + //旋转画布 + this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale); + this.data.ctx.rotate(this.data.angle * Math.PI / 180); + this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height); + this.data.ctx.draw(false, () => { + callback && callback(); + }); + } + if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) { + //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方 + this.setData({ + _canvas_height: this.data.height, + _canvas_width: this.data.width, + }, () => { + //延迟40毫秒防止点击过快出现拉伸或裁剪过多 + setTimeout(() => { + draw(); + }, 40); + }); + } else { + draw(); + } + }, + //裁剪框处理 + _cutTouchMove(e) { + if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) { + if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return; + //节流 + this.data.MOVE_THROTTLE_FLAG = false; + this._move_throttle(); + let width = this.data.width, + height = this.data.height, + cut_top = this.data.cut_top, + cut_left = this.data.cut_left, + size_correct = () => { + width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width; + height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height; + }, + size_inspect = () => { + if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) { + size_correct(); + return false; + } else { + size_correct(); + return true; + } + }; + height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY)); + switch (this.data.CUT_START.corner) { + case 1: + width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width); + break + case 2: + width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height) + cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width) + break + case 3: + width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height); + break + case 4: + width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + break + } + if (!this.data.disable_width && !this.data.disable_height) { + this.setData({ + width: width, + cut_left: cut_left, + height: height, + cut_top: cut_top, + }) + } else if (!this.data.disable_width) { + this.setData({ + width: width, + cut_left: cut_left + }) + } else if (!this.data.disable_height) { + this.setData({ + height: height, + cut_top: cut_top + }) + } + this._imgMarginDetectionScale(); + } + }, + _cutTouchStart(e) { + let currentX = e.touches[0].clientX; + let currentY = e.touches[0].clientY; + let cutbox_top4 = this.data.cut_top + this.data.height - 30; + let cutbox_bottom4 = this.data.cut_top + this.data.height + 20; + let cutbox_left4 = this.data.cut_left + this.data.width - 30; + let cutbox_right4 = this.data.cut_left + this.data.width + 30; + + let cutbox_top3 = this.data.cut_top - 30; + let cutbox_bottom3 = this.data.cut_top + 30; + let cutbox_left3 = this.data.cut_left + this.data.width - 30; + let cutbox_right3 = this.data.cut_left + this.data.width + 30; + + let cutbox_top2 = this.data.cut_top - 30; + let cutbox_bottom2 = this.data.cut_top + 30; + let cutbox_left2 = this.data.cut_left - 30; + let cutbox_right2 = this.data.cut_left + 30; + + let cutbox_top1 = this.data.cut_top + this.data.height - 30; + let cutbox_bottom1 = this.data.cut_top + this.data.height + 30; + let cutbox_left1 = this.data.cut_left - 30; + let cutbox_right1 = this.data.cut_left + 30; + if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + x: currentX, + y: currentY, + corner: 4 + } + } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + x: currentX, + y: currentY, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + corner: 3 + } + } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + x: currentX, + y: currentY, + corner: 2 + } + } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + x: currentX, + y: currentY, + corner: 1 + } + } + }, + _cutTouchEnd(e) { + this._moveStop(); + this.data._flag_cut_touch = false; + }, + //停止移动时需要做的操作 + _moveStop() { + //清空之前的自动居中延迟函数并添加最新的 + clearTimeout(this.data.TIME_CUT_CENTER); + this.data.TIME_CUT_CENTER = setTimeout(() => { + //动画启动 + if (!this.data._cut_animation) { + this.setData({ + _cut_animation: true + }); + } + this.setCutCenter(); + }, 1000) + //清空之前的背景变化延迟函数并添加最新的 + clearTimeout(this.data.TIME_BG); + this.data.TIME_BG = setTimeout(() => { + if (this.data._flag_bright) { + this.setData({ + _flag_bright: false + }); + } + }, 2000) + }, + //移动中 + _moveDuring() { + //清空之前的自动居中延迟函数 + clearTimeout(this.data.TIME_CUT_CENTER); + //清空之前的背景变化延迟函数 + clearTimeout(this.data.TIME_BG); + //高亮背景 + if (!this.data._flag_bright) { + this.setData({ + _flag_bright: true + }); + } + }, + //监听器 + _watcher() { + Object.keys(this.data).forEach(v => { + this._observe(this.data, v, this.data.watch[v]); + }) + }, + _observe(obj, key, watchFun) { + var val = obj[key]; + Object.defineProperty(obj, key, { + configurable: true, + enumerable: true, + set: (value) => { + val = value; + watchFun && watchFun(val, this); + }, + get() { + if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) { + let ret = parseFloat(parseFloat(val).toFixed(3)); + if (typeof val == "string" && val.indexOf("%") != -1) { + ret += '%'; + } + return ret; + } + return val; + } + }) + }, + _preventTouchMove() {} } - } -}) +}) \ No newline at end of file diff --git a/src/image-cropper.json b/src/image-cropper.json index 32640e0dc44a0e9b160f85b9124714ea071ae7b6..d577adeaf94bed8407ab4f2560b1ddff63dd8bdd 100644 --- a/src/image-cropper.json +++ b/src/image-cropper.json @@ -1,3 +1,3 @@ { - "component": true + "component": true } \ No newline at end of file diff --git a/src/image-cropper.wxml b/src/image-cropper.wxml index 12079bbb2751818e7446a2bcae7eb8ca9b4afa3e..a4a752685797d8389f0cbd4ee20888d6108d11c6 100644 --- a/src/image-cropper.wxml +++ b/src/image-cropper.wxml @@ -1,24 +1,24 @@ - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - + - + diff --git a/src/image-cropper.wxss b/src/image-cropper.wxss index 531213b8a79cf4915242a738c9757320e04a7294..b52c12aebd6735d94577ec61708683a7d35e241c 100644 --- a/src/image-cropper.wxss +++ b/src/image-cropper.wxss @@ -1,123 +1,143 @@ -.image-cropper{ - background:rgba(14, 13, 13,.8); - position: fixed; - top:0; - left:0; - width:100vw; - height:100vh; - z-index: 1; -} -.image-cropper .main{ - position: absolute; - width:100vw; - height:100vh; - overflow: hidden; -} -.image-cropper .content{ - z-index: 9; - position: absolute; - width:100vw; - height:100vh; - display: flex; - flex-direction:column; - pointer-events:none; -} -.image-cropper .bg_black{ - background: rgba(0, 0, 0, 0.8)!important; -} -.image-cropper .bg_gray{ - background: rgba(0, 0, 0, 0.45); - transition-duration: .35s; -} -.image-cropper .content>.content_top{ - pointer-events:none; -} -.image-cropper .content>.content_middle{ - display: flex; - height: 200px; - width:100%; -} -.image-cropper .content_middle_middle{ - width:200px; - box-sizing:border-box; - position: relative; - transition-duration: .3s; -} -.image-cropper .content_middle_right{ - flex: auto; -} -.image-cropper .content>.content_bottom{ - flex: auto; -} -.image-cropper .img{ - z-index: 2; - top:0; - left:0; - position: absolute; - border:none; - width:100%; - backface-visibility: hidden; - transform-origin:center; -} -.image-cropper .image-cropper-canvas{ - position: fixed; - background: white; - width:150px; - height:150px; - z-index: 10; - top:-200%; - pointer-events:none; -} -.image-cropper .border{ - background: white; - pointer-events:auto; - position:absolute; -} -.image-cropper .border-top-left{ - left:-2.5px; - top:-2.5px; - height:2.5px; - width:33rpx; -} -.image-cropper .border-top-right{ - right:-2.5px; - top:-2.5px; - height:2.5px; - width:33rpx; -} -.image-cropper .border-right-top{ - top:-1px; - width:2.5px; - height:30rpx; - right:-2.5px; -} -.image-cropper .border-right-bottom{ - width:2.5px; - height:30rpx; - right:-2.5px; - bottom:-1px; -} -.image-cropper .border-bottom-left{ - height:2.5px; - width:33rpx; - bottom:-2.5px; - left:-2.5px; -} -.image-cropper .border-bottom-right{ - height:2.5px; - width:33rpx; - bottom:-2.5px; - right:-2.5px; -} -.image-cropper .border-left-top{ - top:-1px; - width:2.5px; - height:30rpx; - left:-2.5px; -} -.image-cropper .border-left-bottom{ - width:2.5px; - height:30rpx; - left:-2.5px; - bottom:-1px; +.image-cropper { + background: rgba(14, 13, 13, .8); + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1; +} + +.image-cropper .main { + position: absolute; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +.image-cropper .content { + z-index: 9; + position: absolute; + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + pointer-events: none; +} + +.image-cropper .bg_black { + background: rgba(0, 0, 0, 0.8) !important; +} + +.image-cropper .bg_gray { + background: rgba(0, 0, 0, 0.45); + transition-duration: .35s; +} + +.image-cropper .content>.content_top { + pointer-events: none; +} + +.image-cropper .content>.content_middle { + display: flex; + height: 200px; + width: 100%; +} + +.image-cropper .content_middle_middle { + width: 200px; + box-sizing: border-box; + position: relative; + transition-duration: .3s; +} + +.image-cropper .content_middle_right { + flex: auto; +} + +.image-cropper .content>.content_bottom { + flex: auto; +} + +.image-cropper .img { + z-index: 2; + top: 0; + left: 0; + position: absolute; + border: none; + width: 100%; + backface-visibility: hidden; + transform-origin: center; +} + +.image-cropper .image-cropper-canvas { + position: fixed; + background: white; + width: 150px; + height: 150px; + z-index: 10; + top: -200%; + pointer-events: none; +} + +.image-cropper .border { + background: white; + pointer-events: auto; + position: absolute; +} + +.image-cropper .border-top-left { + left: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-top-right { + right: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-right-top { + top: -1px; + width: 2.5px; + height: 30rpx; + right: -2.5px; +} + +.image-cropper .border-right-bottom { + width: 2.5px; + height: 30rpx; + right: -2.5px; + bottom: -1px; +} + +.image-cropper .border-bottom-left { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + left: -2.5px; +} + +.image-cropper .border-bottom-right { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + right: -2.5px; +} + +.image-cropper .border-left-top { + top: -1px; + width: 2.5px; + height: 30rpx; + left: -2.5px; +} + +.image-cropper .border-left-bottom { + width: 2.5px; + height: 30rpx; + left: -2.5px; + bottom: -1px; }