小程序canvas截图组件

最近做一个小程序的过程中,需要用到截图功能,网上搜了一下,发现没有符合要求的,就自己搞了个组件,方便复用。

目前功能很简单,传入宽高和图片路径即可,宽高是为了计算截图的比例,只支持缩放和移动。

实现思路是:

1.模拟一个截取框;2.移动图片位置,缩放图片;3.获取图片在其中的位置(left,top,width,height);4.使用canvas绘制图片,然后截取就ok了。

其中第二步的缩放图片比较麻烦,缩放中心点以及平滑缩放

以下是我的实现方式

wxml:

<!--component/picPro/picPro.wxml-->
<scroll-view class=‘body‘ hidden="{{hidden}}">
<view class=‘flex-column flex-between full-height full-width‘ bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend">
<view class=‘bg_dark out_item‘></view>

<view class=‘flex-row main flex-between‘ style=‘height:{{(windowWidth - margin.left - margin.right)/ratio + "px"}}‘>
<view class=‘bg_dark main_item full-height‘ style=‘width:{{margin.left  + "px"}}‘></view>

<view class=‘inner relative full-width‘ id=‘showArea‘>
<image class=‘absolute img‘ src=‘{{src}}‘ style="width:{{img.width}}px;height:{{img.height}}px;left:{{img.left}}px;top:{{img.top}}px;"></image>
<canvas canvas-id=‘imgCanvas‘ class=‘absolute img_canvas full-height full-width‘ />
<view class=‘absolute inner_item left_top‘></view>
<view class=‘absolute inner_item right_top‘></view>
<view class=‘absolute inner_item right_bottom‘></view>
<view class=‘absolute inner_item left_bottom‘></view>
</view>

<view class=‘bg_dark main_item full-height‘ style=‘width:{{margin.right + "px"}}‘></view>
</view>

<view class=‘bg_dark out_item flex-column flex-end‘>
 <view class=‘flex-around text_white text_bg‘>
<view catchtap=‘outputImg‘ data-type=‘1‘><text>重新上传</text></view>
<view catchtap=‘getImg‘><text>选择图片</text></view>
</view>
</view>
<!--  -->
<view class=‘absolute full-width full-height bg_black‘></view>
</view>
</scroll-view>

wxss:(其中引入了一个公共样式,关于flex布局的,看样式名也能猜到)

/* component/picPro/picPro.wxss */
@import ‘../../resource/style/flex.wxss‘;
.body{
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
.text_white{
  color: white;
}
.main{
}
.out_item{
  width: 100%;
  height: 100%;
  flex: 1;
}
.bg_dark{
  background-color: rgba(0, 0, 0, 0.85)
}
.main_item{
  width: 15px;
}
.inner{
  outline: 3rpx solid white;
  background-color: rgba(0, 0, 0, 0.12);
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.5) inset;
}
.inner_item{
  width: 8px;
  height: 8px;
}
.inner_item.left_top{
  border-left: 3px solid white;
  border-top: 3px solid white;
  left: -3px;
  top: -3px;
}
.inner_item.right_top{
  border-right: 3px solid white;
  border-top: 3px solid white;
  right: -3px;
  top: -3px;
}
.inner_item.right_bottom{
  border-right: 3px solid white;
  border-bottom: 3px solid white;
  right: -3px;
  bottom: -3px;
}
.inner_item.left_bottom{
  border-left: 3px solid white;
  border-bottom: 3px solid white;
  left: -3px;
  bottom: -3px;
}
.img{
  z-index: -1;
}
.bg_black{
  background-color:black;
  z-index: -2;
}
.text_bg{
  padding-bottom: 2em;
  font-size: 0.9em;
}

.img_canvas{
  opacity: 0.5;
}
.newImg{
  z-index: 2
}

js:

  1 // component/picPro/picPro.js
  2 const state = {
  3   // 可用区域body
  4   window: { width: 0, height: 0 },
  5   // 原始图片信息
  6   originImg: { width: 0, height: 0 },
  7   // 第一次图片缩放信息
  8   firstScaleImg: { width: 0, height: 0 },
  9   // 截取区域信息
 10   interArea: { width: 0, height: 0 },
 11   // 单手触摸位置
 12   touchLast: { x: 0, y: 0 },
 13   // 滑动距离
 14   touchMove: { x: 0, y: 0 },
 15   // 滑动离开时图片状态
 16   moveImgState: {
 17     width: 0,
 18     height: 0,
 19     top: 0,
 20     left: 0,
 21   },
 22   // 双手触摸位置
 23   touchList: [{ x: 0, y: 0 }, { x: 0, y: 0 }],
 24   // 图片缩放比例
 25   scale: 1,
 26 }
 27 Component({
 28   /**
 29    * 组件的属性列表
 30    */
 31   properties: {
 32     //宽(非实际值)
 33     width: {
 34       type: Number,
 35       value: 600
 36     },
 37     //高
 38     height: {
 39       type: Number,
 40       value: 300
 41     },
 42     //图片路径
 43     src: {
 44       type: String,
 45       value: ""
 46     },
 47     //显示隐藏
 48     hidden: {
 49       type: Boolean,
 50       value: false
 51     },
 52     //截取框的信息
 53     margin: {
 54       type: Object,
 55       value: {
 56         left: 15,
 57         right: 15,
 58         top: 200,
 59         bottom: 200,
 60       }
 61     }
 62   },
 63
 64   ready() {
 65     this.initialize();
 66     // const canvas = wx.createCanvasContext(‘imgCanvas‘, this);
 67     // canvas.draw(false, () => { console.log(‘ccc‘) }, this);
 68   },
 69
 70   /**
 71    * 组件的初始数据
 72    */
 73   data: {
 74     touchRange: 8,
 75     img: {
 76       width: 0,
 77       height: 0,
 78       top: 0,
 79       left: 0,
 80     },
 81     canvas: {},
 82     ratio: 0,
 83     originImg: {
 84       width: 0,
 85       height: 0
 86     }
 87   },
 88
 89   /**
 90    * 组件的方法列表
 91    */
 92   methods: {
 93     touchstart(e) {
 94       // console.log("touchstart", e);
 95
 96     },
 97     touchmove(e) {
 98       if (e.touches.length === 1) { this.singleSlip(e.touches[0]) } else {
 99         this.doubleSlip(e.touches)
100       }
101     },
102     touchend(e) {
103       // console.log("touchend", e);
104       const x = 0, y = 0;
105       state.touchLast = { x, y };
106       state.touchMove = { x, y };
107       state.touchList = [{ x, y }, { x, y }];
108       state.moveImgState = this.data.img;
109       // console.log(this.data.img);
110     },
111     // 单手滑动操作
112     singleSlip(e) {
113       const { clientX: x, clientY: y } = e;
114       const that = this;
115       if (state.touchLast.x && state.touchLast.y) {
116         state.touchMove = { x: x - state.touchLast.x, y: y - state.touchLast.y };
117         state.touchLast = { x, y };
118         const move = (_x = false, _y = false) => {
119           const bottom = that.data.img.height + that.data.img.top;
120           const right = that.data.img.width + that.data.img.left;
121           const h = state.interArea.height;
122           const w = state.interArea.width;
123           const param = {};
124           if (_x) {
125             if (right > w && that.data.img.left < 0) {
126               param.left = that.data.img.left + state.touchMove.x * 0.1
127             } else if (right <= w && state.touchMove.x > 0) {
128               param.left = that.data.img.left + state.touchMove.x * 0.1
129             } else if (that.data.img.left >= 0 && state.touchMove.x < 0) {
130               param.left = that.data.img.left + state.touchMove.x * 0.1
131             }
132           };
133           if (_y) {
134             if (bottom > h && that.data.img.top < 0) {
135               param.top = that.data.img.top + state.touchMove.y * 0.1
136             } else if (bottom <= h && state.touchMove.y > 0) {
137               param.top = that.data.img.top + state.touchMove.y * 0.1
138             } else if (that.data.img.top >= 0 && state.touchMove.y < 0) {
139               param.top = that.data.img.top + state.touchMove.y * 0.1
140             }
141           };
142           // console.log(param);
143           that.setImgPos(param)
144         };
145         if (state.scale == 1) {
146           if (that.data.img.width == state.interArea.width) {
147             move(false, true)
148           } else {
149             move(true, false)
150           }
151         } else {
152           move(true, true)
153         }
154       } else {
155         state.touchLast = { x, y }
156       }
157     },
158     // 双手缩放操作
159     doubleSlip(e) {
160       const that = this;
161       const { clientX: x0, clientY: y0 } = e[0];
162       const { clientX: x1, clientY: y1 } = e[1];
163       if (state.touchList[0].x && state.touchList[0].y) {
164         let changeScale = (Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)) - Math.sqrt((state.touchList[1].x - state.touchList[0].x) * (state.touchList[1].x - state.touchList[0].x) + (state.touchList[1].y - state.touchList[0].y) * (state.touchList[1].y - state.touchList[0].y))) * 0.0005;
165         changeScale = changeScale >= 1.5 ? 1.5 : (changeScale <= -1 ? -1 : changeScale);
166         state.scale = that.data.img.width / state.firstScaleImg.width < 1 ? 1 : (state.scale > 2.5 ? 2.5 : 1 + changeScale);
167         let width = state.firstScaleImg.width * (state.scale - 1) + state.moveImgState.width;
168         width = width < state.firstScaleImg.width ? state.firstScaleImg.width : width;
169         let height = state.firstScaleImg.height * (state.scale - 1) + state.moveImgState.height;
170         height = height < state.firstScaleImg.height ? state.firstScaleImg.height : height;
171         let left = width * (1 - state.scale) / 4 + state.moveImgState.left;
172         left = left * (-1) > width - state.interArea.width ? state.interArea.width - width: left > 0 ? 0 : left;
173         let top = height * (1 - state.scale) / 4 + state.moveImgState.top;
174         top = top * (-1) > height - state.interArea.height ?state.interArea.height - height  : top > 0 ? 0 : top;
175         const setImgObj = { width, height, left, top };
176         that.setImgPos(setImgObj)
177       } else {
178         state.touchList = [{ x: x0, y: y0 }, { x: x1, y: y1 }]
179       }
180     },
181     // 获取可用区域宽高
182     getScreenInfo() {
183       const that = this;
184       return new Promise((resolve, reject) => {
185         wx.getSystemInfo({
186           success: function (res) {
187             const { windowHeight, windowWidth } = res;
188             state.window = { windowHeight, windowWidth };
189             that.setData({ windowHeight, windowWidth })
190             // console.log(state.window);
191             resolve(res);
192           },
193         })
194       })
195     },
196     setShowArea() {
197       const that = this;
198       const w = state.window.windowWidth - that.data.margin.left - that.data.margin.right;
199       const h = (that.data.height / that.data.width) * w;
200     },
201     outputImg() {
202       this.setData({
203         hidden: true,
204       })
205     },
206     getImgInfo(path) {
207       return new Promise((resolve, reject) => {
208         wx.getImageInfo({
209           src: path,
210           success(res) {
211             console.log(res);
212             resolve(res);
213           },
214           fail(err) {
215             reject(err)
216           }
217         })
218       })
219     },
220     // 设置图片
221     setImgPos({ width, height, top, left }) {
222       width = width || this.data.img.width;
223       height = height || this.data.img.height;
224       top = top || this.data.img.top;
225       left = left || this.data.img.left
226       this.setData({
227         img: { width, height, top, left }
228       })
229     },
230     // 初始化图片位置大小
231     initialize() {
232       const that = this;
233       const ratio = that.data.width / that.data.height;
234       this.getScreenInfo().then(res => {
235         console.log(res);
236         state.interArea = { width: res.windowWidth - that.data.margin.left - that.data.margin.right + 2, height: (res.windowWidth - that.data.margin.left - that.data.margin.right) / ratio };
237         console.log("interArea", state.interArea)
238         that.getImgInfo(that.data.src).then(imgInfo => {
239           const { width, height } = imgInfo;
240           const imgRatio = width / height;
241           state.originImg = { width, height };
242           that.setData({
243             ratio: ratio
244           });
245           if (imgRatio > ratio) {
246             that.setImgPos({
247               height: state.interArea.height,
248               width: state.interArea.height * imgRatio
249             })
250           } else {
251             that.setImgPos({
252               height: state.interArea.width / imgRatio,
253               width: state.interArea.width,
254             })
255           };
256           state.firstScaleImg = { width: that.data.img.width, height: that.data.img.height }
257         });
258       });
259     },
260     // 截图
261     getImg(){
262       const that = this;
263       // console.log(‘dudu‘, that.data.img);
264       const canvas = wx.createCanvasContext(‘imgCanvas‘, this);
265       const {width,height,left,top} = that.data.img;
266       const saveImg = ()=>{
267         console.log(‘开始截取图片‘);
268         wx.canvasToTempFilePath({
269           canvasId:"imgCanvas",
270           success(res){
271             // console.log(res);
272             that.setData({
273               hidden:true,
274               // src:""
275             });
276             that.triggerEvent("putimg", { imgUrl: res.tempFilePath},{});
277           },
278           fail(err){
279             console.log(err)
280           }
281         },that)
282       };
283       canvas.drawImage(that.data.src, left, top, width, height);
284       canvas.draw(false, () => { saveImg() }, that)
285     }
286   }
287 })

引用的时候除了宽高路径以外,需要wx:if;如果不卸载组件,会出现只能截一次的bug

因为小程序里面没有类似vue中catch的观测数据变化的东西,也不想为了个组件专门去搞一个,就用这种方式代替了,嘻嘻,好敷衍。。

原文地址:https://www.cnblogs.com/ahongdepu/p/8683519.html

时间: 2024-11-08 17:42:23

小程序canvas截图组件的相关文章

微信小程序_基础组件大全

微信小程序_基础组件 微信小程序为小程序开发者提供了一系列小程序基础组件,开发者可以通过组合这些小程序基础组件进行微信小程序的快速开发. 微信小程序组件是什么?微信小程序组件怎么用? 小程序组件是视图层的基本组成单元.小程序组件自带一些功能与微信风格的样式.一个小程序组件通常包括开始标签和结束标签,属性用来修饰这个组件,内容在两个标签之内. <tagname property="value"> Content goes here ... </tagename>

小程序聊天会话组件

效果图 场景 用于在线客服的聊天对话等 一.布局圈点 1.三角箭头 绘制一个26rpx*26rpx矩形,使它旋转45度,然后隐去对半,形成气泡上的直角三角形. <!-- 画三角箭头 --> <view class="triangle" style="{{item.myself == 1 ? 'right: 140rpx; background: #7ECB4B' : 'left: 140rpx;'}}"></view> /* 三角

小程序解决方案 Westore - 组件、纯组件、插件开发

数据流转 先上一张图看清 Westore 怎么解决小程序数据难以管理和维护的问题: 非纯组件的话,可以直接省去 triggerEvent 的过程,直接修改 store.data 并且 update,形成缩减版单向数据流. Github: https://github.com/dntzhang/westore 组件 这里说的组件便是自定义组件,使用原生小程序的开发格式如下: Component({ properties: { }, data: { }, methods: { } }) 使用 Wes

微信小程序 | canvas绘图

1.新的尺寸单位 rpx rpx(responsive pixel): 可以根据屏幕宽度进行自适应. 规定屏幕宽为750rpx.如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素. 设备 rpx换算px (屏幕宽度/750): px换算rpx (750/屏幕宽度) 2.小程序canvas总结 小程序绘制的单位是px, 基础知识点请参考文档 : canvas中存在一个坐标系,二维的,左上

「小程序JAVA实战」小程序的基础组件(24)

转自:https://idig8.com/2018/08/12/xiaochengxu-chuji-24/ 来说下 ,小程序的基础组件.源码:https://github.com/limingios/wxProgram.git 中的No.11 基础组件 icon图标组件 rich-text 富文本组件 text 文本组件 progress 进度条组件 icon图标组件 官方介绍>https://developers.weixin.qq.com/miniprogram/dev/component/

微信小程序导入Vant-Weapp组件库及出错处理

微信小程序导入Vant-Weapp组件库及出错处理一.下载Node.js*链接:https://nodejs.org/en/推荐选择LST的8.0以上版本,下载安装即可,安装完成后可以进行验证cmd打开终端,输入vant-v出现对应版本即为安装成功,也可输入npm -v查看对应的npm版本 二.在微信开发工具做对应操作 链接:https://youzan.github.io/vant-weapp/#/intro 后续可根据开发指南进行操作 三.安装Vant组件库选中miniprogram文件,右

微信小程序 canvas 绘图问题总结

业务中碰到微信小程序需要生成海报进行朋友圈分享,这个是非常常见的功能,没想到实际操作的时候花了整整一天一夜才搞好,微信的 canvas 绘图实在是太难用了,官方快点优化一下吧. 业务非常简单,只需要将用到的图片,文案素材拼装到一张图片,保存到本地就可以了. 首先创建画布,将一张网上的图片画到画布上. const ctx = wx.createCanvasContext('shareCanvas'); ctx.drawImage("https://img3.doubanio.com/view/ph

微信小程序——自定义图标组件

字体图标在网页中非常常见了.为了方便在小程序里面重复使用,自定义了一个图标组件,方便控制它的大小,颜色,自定义点击事件. 自定义图标组件的代码如下: 下面的代码是icon文件夹下面的4个文件 index.wxml: <view class="custom-class ss-font ss-icon-{{ name }}" style="{{ color ? 'color: ' + color : '' }}; {{ size ? 'font-size: ' + size

小程序 canvas 2d 新接口 绘制带小程序码的海报图

截止2020.3.26,小程序官方文档中,有两种绘制方式:Canvas 2D.webGL 文档地址:https://developers.weixin.qq.com/miniprogram/dev/component/canvas.html 而开发者工具中,官方推荐使用性能更好的2d模式,用法如下所示: <canvas type="2d" id="myCanvas"></canvas> 但是网上大多数教程都是使用旧的接口,如: <can