小程序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:;
right:;
bottom:;
left:;
}
.text_white{
color: white;
}
.main{
}
.out_item{
width: 100%;
height: 100%;
flex:;
}
.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:
// component/picPro/picPro.js
const state = {
// 可用区域body
window: { width: 0, height: 0 },
// 原始图片信息
originImg: { width: 0, height: 0 },
// 第一次图片缩放信息
firstScaleImg: { width: 0, height: 0 },
// 截取区域信息
interArea: { width: 0, height: 0 },
// 单手触摸位置
touchLast: { x: 0, y: 0 },
// 滑动距离
touchMove: { x: 0, y: 0 },
// 滑动离开时图片状态
moveImgState: {
width: 0,
height: 0,
top: 0,
left: 0,
},
// 双手触摸位置
touchList: [{ x: 0, y: 0 }, { x: 0, y: 0 }],
// 图片缩放比例
scale: 1,
}
Component({
/**
* 组件的属性列表
*/
properties: {
//宽(非实际值)
width: {
type: Number,
value: 600
},
//高
height: {
type: Number,
value: 300
},
//图片路径
src: {
type: String,
value: ""
},
//显示隐藏
hidden: {
type: Boolean,
value: false
},
//截取框的信息
margin: {
type: Object,
value: {
left: 15,
right: 15,
top: 200,
bottom: 200,
}
}
}, ready() {
this.initialize();
// const canvas = wx.createCanvasContext('imgCanvas', this);
// canvas.draw(false, () => { console.log('ccc') }, this);
}, /**
* 组件的初始数据
*/
data: {
touchRange: 8,
img: {
width: 0,
height: 0,
top: 0,
left: 0,
},
canvas: {},
ratio: 0,
originImg: {
width: 0,
height: 0
}
}, /**
* 组件的方法列表
*/
methods: {
touchstart(e) {
// console.log("touchstart", e); },
touchmove(e) {
if (e.touches.length === 1) { this.singleSlip(e.touches[0]) } else {
this.doubleSlip(e.touches)
}
},
touchend(e) {
// console.log("touchend", e);
const x = 0, y = 0;
state.touchLast = { x, y };
state.touchMove = { x, y };
state.touchList = [{ x, y }, { x, y }];
state.moveImgState = this.data.img;
// console.log(this.data.img);
},
// 单手滑动操作
singleSlip(e) {
const { clientX: x, clientY: y } = e;
const that = this;
if (state.touchLast.x && state.touchLast.y) {
state.touchMove = { x: x - state.touchLast.x, y: y - state.touchLast.y };
state.touchLast = { x, y };
const move = (_x = false, _y = false) => {
const bottom = that.data.img.height + that.data.img.top;
const right = that.data.img.width + that.data.img.left;
const h = state.interArea.height;
const w = state.interArea.width;
const param = {};
if (_x) {
if (right > w && that.data.img.left < 0) {
param.left = that.data.img.left + state.touchMove.x * 0.1
} else if (right <= w && state.touchMove.x > 0) {
param.left = that.data.img.left + state.touchMove.x * 0.1
} else if (that.data.img.left >= 0 && state.touchMove.x < 0) {
param.left = that.data.img.left + state.touchMove.x * 0.1
}
};
if (_y) {
if (bottom > h && that.data.img.top < 0) {
param.top = that.data.img.top + state.touchMove.y * 0.1
} else if (bottom <= h && state.touchMove.y > 0) {
param.top = that.data.img.top + state.touchMove.y * 0.1
} else if (that.data.img.top >= 0 && state.touchMove.y < 0) {
param.top = that.data.img.top + state.touchMove.y * 0.1
}
};
// console.log(param);
that.setImgPos(param)
};
if (state.scale == 1) {
if (that.data.img.width == state.interArea.width) {
move(false, true)
} else {
move(true, false)
}
} else {
move(true, true)
}
} else {
state.touchLast = { x, y }
}
},
// 双手缩放操作
doubleSlip(e) {
const that = this;
const { clientX: x0, clientY: y0 } = e[0];
const { clientX: x1, clientY: y1 } = e[1];
if (state.touchList[0].x && state.touchList[0].y) {
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;
changeScale = changeScale >= 1.5 ? 1.5 : (changeScale <= -1 ? -1 : changeScale);
state.scale = that.data.img.width / state.firstScaleImg.width < 1 ? 1 : (state.scale > 2.5 ? 2.5 : 1 + changeScale);
let width = state.firstScaleImg.width * (state.scale - 1) + state.moveImgState.width;
width = width < state.firstScaleImg.width ? state.firstScaleImg.width : width;
let height = state.firstScaleImg.height * (state.scale - 1) + state.moveImgState.height;
height = height < state.firstScaleImg.height ? state.firstScaleImg.height : height;
let left = width * (1 - state.scale) / 4 + state.moveImgState.left;
left = left * (-1) > width - state.interArea.width ? state.interArea.width - width: left > 0 ? 0 : left;
let top = height * (1 - state.scale) / 4 + state.moveImgState.top;
top = top * (-1) > height - state.interArea.height ?state.interArea.height - height : top > 0 ? 0 : top;
const setImgObj = { width, height, left, top };
that.setImgPos(setImgObj)
} else {
state.touchList = [{ x: x0, y: y0 }, { x: x1, y: y1 }]
}
},
// 获取可用区域宽高
getScreenInfo() {
const that = this;
return new Promise((resolve, reject) => {
wx.getSystemInfo({
success: function (res) {
const { windowHeight, windowWidth } = res;
state.window = { windowHeight, windowWidth };
that.setData({ windowHeight, windowWidth })
// console.log(state.window);
resolve(res);
},
})
})
},
setShowArea() {
const that = this;
const w = state.window.windowWidth - that.data.margin.left - that.data.margin.right;
const h = (that.data.height / that.data.width) * w;
},
outputImg() {
this.setData({
hidden: true,
})
},
getImgInfo(path) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: path,
success(res) {
console.log(res);
resolve(res);
},
fail(err) {
reject(err)
}
})
})
},
// 设置图片
setImgPos({ width, height, top, left }) {
width = width || this.data.img.width;
height = height || this.data.img.height;
top = top || this.data.img.top;
left = left || this.data.img.left
this.setData({
img: { width, height, top, left }
})
},
// 初始化图片位置大小
initialize() {
const that = this;
const ratio = that.data.width / that.data.height;
this.getScreenInfo().then(res => {
console.log(res);
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 };
console.log("interArea", state.interArea)
that.getImgInfo(that.data.src).then(imgInfo => {
const { width, height } = imgInfo;
const imgRatio = width / height;
state.originImg = { width, height };
that.setData({
ratio: ratio
});
if (imgRatio > ratio) {
that.setImgPos({
height: state.interArea.height,
width: state.interArea.height * imgRatio
})
} else {
that.setImgPos({
height: state.interArea.width / imgRatio,
width: state.interArea.width,
})
};
state.firstScaleImg = { width: that.data.img.width, height: that.data.img.height }
});
});
},
// 截图
getImg(){
const that = this;
// console.log('dudu', that.data.img);
const canvas = wx.createCanvasContext('imgCanvas', this);
const {width,height,left,top} = that.data.img;
const saveImg = ()=>{
console.log('开始截取图片');
wx.canvasToTempFilePath({
canvasId:"imgCanvas",
success(res){
// console.log(res);
that.setData({
hidden:true,
// src:""
});
that.triggerEvent("putimg", { imgUrl: res.tempFilePath},{});
},
fail(err){
console.log(err)
}
},that)
};
canvas.drawImage(that.data.src, left, top, width, height);
canvas.draw(false, () => { saveImg() }, that)
}
}
})
引用的时候除了宽高路径以外,需要wx:if;如果不卸载组件,会出现只能截一次的bug
因为小程序里面没有类似vue中catch的观测数据变化的东西,也不想为了个组件专门去搞一个,就用这种方式代替了,嘻嘻,好敷衍。。
<------------------------以前做的,现在发一下源码,不要吐槽--------------------------->
链接:https://pan.baidu.com/s/1MnTVhdGDmtFLTJdO5m024w
提取码:7yun
小程序canvas截图组件的更多相关文章
- 原创:WeZRender:微信小程序Canvas增强组件
WeZRender是一个微信小程序Canvas增强组件,基于HTML5 Canvas类库ZRender. 使用 WXML: <canvas style="width: 375px; h ...
- 微信小程序--canvas画布实现图片的编辑
技术:微信小程序 概述 上传图片,编辑图片大小,添加文字,改变文字颜色等 详细 代码下载:http://www.demodashi.com/demo/14789.html 概述 微信小程序--ca ...
- 小程序canvas生成海报保存至手机相册
小程序canvas画图保存至手机相册 (1)可直接展示生成的海报 .因手机分辨率不同可能导致生成的海报会有细微差别,这里隐藏canvas海报,页面正常设置海报样式保存时保存隐藏的canvas海报 (2 ...
- 优化版小程序canvas,增加失败逻辑,及完善文字
wxml <view class="shareBox" style="backgound:{{isShow ? '#000' : '#fff'}}" wx ...
- 小程序Canvas性能优化实战
以下内容转载自totoro的文章<小程序Canvas性能优化实战!> 作者:totoro 链接:https://blog.totoroxiao.com/canvas-perf-mini/ ...
- [技术博客]海报图片生成——小程序canvas画布
目录 背景介绍 canvas简介 代码实现 难点讲解 圆角矩形裁剪失败之PS的妙用 编码不要过硬 对过长的文字进行截取 真机首次生成时字体不对 drawImage只能使用本地图片 背景介绍 目标:利用 ...
- 微信小程序之swiper组件高度自适应
微信小程序之swiper组件高度自适应 要求: (顶部广告栏 ) 改变swiper组件的固定高度,使之随内部每张图片的高度做自适应 原理: 图片加载完之后,获取图片的原始宽高,根据宽高比,计算出适应后 ...
- 记录一下小程序canvas
小程序canvas学习 效果图: .wxml <canvas style="width: 100vw; height: 100vh;" canvas-id="fir ...
- 微信小程序中的组件使用1
不管是vue还是react中,都在强调组件思想,同样,在微信小程序中也是使用组件思想来实现页面复用的,下面就简单介绍一下微信小程序中的组件思想. 组件定义与使用 要使用组件,首先需要有组件页面和使用组 ...
随机推荐
- HUST1017 Exact cover —— Dancing Links 精确覆盖 模板题
题目链接:https://vjudge.net/problem/HUST-1017 1017 - Exact cover 时间限制:15秒 内存限制:128兆 自定评测 7673 次提交 3898 次 ...
- express 中文文档
express() 创建一个express应用程序 var express = require('express'); var app = express(); app.get('/', functi ...
- jQuery插件之ajaxFileUpload API文档
ajaxFileUpload是一个异步上传文件的jQuery插件. 语法:$.ajaxFileUpload([options]) options参数说明: 1.url 上传处理程序地址. 2,fil ...
- Java 绘制环形的文字 (Circle Text Demo)
1. [代码]CircleTextDemo.java import java.awt.*;import java.awt.event.*;import java.awt.geom.*; /** ...
- codeforces 450B. Jzzhu and Sequences 解题报告
题目链接:http://codeforces.com/problemset/problem/450/B 题目意思:给出 f1 和 f2 的值,以及n,根据公式:fi = fi-1 + fi+1,求出f ...
- ASP.NET统计图表控件
近来客户需要将前段时间开发的统计信息用图表展示出来,还要多个图表类型,例如:柱状图.饼图.曲线图.三维图等等.在网上google了一下,发现了三个(也许更多)可以使用的控件.下面我们一起看看这三个控件 ...
- CKD 实现
主要功能: 1.新物料(部品号)的入库管理 部品号的验证.描述.品名.重量.单价等 2.部品号-供应商的核对 校验部品号/供应商的对应情况.入库.移除等 3.BOM清单的导入 基础清单的导入 4.订单 ...
- C#面向对象之数据库(理论、插入、修改、删除、查询)
1.数据库的作用:不仅仅是存储,更重要的是将数据进行存储以后怎么样才能方便快捷的查询修改 2.数据库的特点:海量存储.查找速度快.并发性问题控制.安全性.数据完整性(保存在数据库中的数据是正确的.真是 ...
- Hadoop 三大调度器源码分析及编写自己的调度器
如要转载,请注上作者和出处. 由于能力有限,如有错误,请大家指正. 须知: 我们下载的是hadoop-2.7.3-src 源码. 这个版本默认调度器是Capacity调度器. 在2.0.2-alph ...
- python __builtins__ bytes类 (8)
8.'bytes', 字符串转换成字节流.第一个传入参数是要转换的字符串,第二个参数按什么编码转换为字节. class bytes(object) | bytes(iterable_of_ints) ...