最近做一个小程序的过程中,需要用到截图功能,网上搜了一下,发现没有符合要求的,就自己搞了个组件,方便复用。
目前功能很简单,传入宽高和图片路径即可,宽高是为了计算截图的比例,只支持缩放和移动。
实现思路是:
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://pan.baidu.com/s/1MnTVhdGDmtFLTJdO5m024w
提取码:7yun
更多专业前端知识,请上 【猿2048】www.mk2048.com