热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

canvas画条形图微信小程序_微信小程序canvas画布实现图片的编辑

概述微信小程序--canvas画布实现图片的编辑详细一、前期准备工作1、基本需求。实现上传图片实现图片编辑实现添加文字实现导出图片2、案例目录结构二、程序实现具体步骤1.index

概述

微信小程序--canvas画布实现图片的编辑

详细

一、前期准备工作

1、基本需求。

实现上传图片

实现图片编辑

实现添加文字

实现导出图片

2、案例目录结构

二、程序实现具体步骤

1.index.js代码(canvas-drag)

// components/canvas-drag/index.js

const dragGraph = function ({ x, y, w, h, type, text, fontSize = 20, color = 'red', url }, canvas, factor) {

if (type === 'text') {

canvas.setFontSize(fontSize);

const textWidth = canvas.measureText(this.text).width;

const textHeight = fontSize + 10;

const halfWidth = textWidth / 2;

const halfHeight = textHeight / 2;

this.x = x + halfWidth;

this.y = y + halfHeight;

} else {

this.x = x;

this.y = y;

}

this.w = w;

this.h = h;

this.fileUrl = url;

this.text = text;

this.fontSize = fontSize;

this.color = color;

this.ctx = canvas;

this.rotate = 0;

this.type = type;

this.selected = true;

this.factor = factor;

this.MIN_WIDTH = 50;

this.MIN_FONTSIZE = 10;

}

dragGraph.prototype = {

/**

* 绘制元素

*/

paint() {

this.ctx.save();

// TODO 剪切

// this._drawRadiusRect(0, 0, 700, 750, 300);

// this.ctx.clip();

// 由于measureText获取文字宽度依赖于样式,所以如果是文字元素需要先设置样式

if (this.type === 'text') {

this.ctx.setFontSize(this.fontSize);

this.ctx.setTextBaseline('middle');

this.ctx.setTextAlign('center');

this.ctx.setFillStyle(this.color);

}

// 选择区域的中心点

this.centerX = this.type === 'text' ? this.x : this.x + (this.w / 2);

this.centerY = this.type === 'text' ? this.y : this.y + (this.h / 2);

// 旋转元素

this.ctx.translate(this.centerX, this.centerY);

this.ctx.rotate(this.rotate * Math.PI / 180);

this.ctx.translate(-this.centerX, -this.centerY);

// 渲染元素

if (this.type === 'text') {

this.ctx.fillText(this.text, this.x, this.y);

} else if (this.type === 'image') {

this.ctx.drawImage(this.fileUrl, this.x, this.y, this.w, this.h);

}

// 如果是选中状态,绘制选择虚线框,和缩放图标、删除图标

if (this.selected) {

this.ctx.setLineDash([10, 10]);

this.ctx.setLineWidth(2);

this.ctx.setStrokeStyle('red');

this.ctx.lineDashOffset = 10;

if (this.type === 'text') {

const textWidth = this.ctx.measureText(this.text).width;

const textHeight = this.fontSize + 10

const halfWidth = textWidth / 2;

const halfHeight = textHeight / 2;

const textX = this.x - halfWidth;

const textY = this.y - halfHeight;

this.ctx.strokeRect(textX, textY, textWidth, textHeight);

this.ctx.drawImage('./icon/close.png', textX - 15, textY - 15, 30, 30);

this.ctx.drawImage('./icon/scale.png', textX + textWidth - 15, textY + textHeight - 15, 30, 30);

} else {

this.ctx.strokeRect(this.x, this.y, this.w, this.h);

this.ctx.drawImage('./icon/close.png', this.x - 15, this.y - 15, 30, 30);

this.ctx.drawImage('./icon/scale.png', this.x + this.w - 15, this.y + this.h - 15, 30, 30);

}

}

this.ctx.restore();

},

/**

* 判断点击的坐标落在哪个区域

* @param {*} x 点击的坐标

* @param {*} y 点击的坐标

*/

isInGraph(x, y) {

const selectW = this.type === 'text' ? this.ctx.measureText(this.text).width : this.w;

const selectH = this.type === 'text' ? this.fontSize + 10 : this.h;

// 删除区域左上角的坐标和区域的高度宽度

const delW = 30;

const delH = 30;

const delX = this.type === 'text' ? this.x - (selectW / 2) : this.x;

const delY = this.type === 'text' ? this.y - (selectH / 2) : this.y;

// 旋转后的删除区域坐标

const transformDelX = this._getTransform(delX, delY, this.rotate - this._getAngle(this.centerX, this.centerY, delX, delY)).x - (delW / 2);

const transformDelY = this._getTransform(delX, delY, this.rotate - this._getAngle(this.centerX, this.centerY, delX, delY)).y - (delH / 2);

// 变换区域左上角的坐标和区域的高度宽度

const scaleW = 30;

const scaleH = 30;

const scaleX = this.type === 'text' ? this.x + (selectW / 2) : this.x + selectW;

const scaleY = this.type === 'text' ? this.y + (selectH / 2) : this.y + selectH;

// 旋转后的变换区域坐标

const transformScaleX = this._getTransform(scaleX, scaleY, this.rotate + this._getAngle(this.centerX, this.centerY, scaleX, scaleY)).x - (scaleW / 2);

const transformScaleY = this._getTransform(scaleX, scaleY, this.rotate + this._getAngle(this.centerX, this.centerY, scaleX, scaleY)).y - (scaleH / 2);

const moveX = this.type === 'text' ? this.x - (selectW / 2) : this.x;

const moveY = this.type === 'text' ? this.y - (selectH / 2) : this.y;

// 测试使用

// this.ctx.setLineWidth(1);

// this.ctx.setStrokeStyle('red');

// this.ctx.strokeRect(transformDelX, transformDelY, delW, delH);

// this.ctx.setLineWidth(1);

// this.ctx.setStrokeStyle('black');

// this.ctx.strokeRect(transformScaleX, transformScaleY, scaleW, scaleH);

if (x - transformScaleX >= 0 && y - transformScaleY >= 0 &&

transformScaleX + scaleW - x >= 0 && transformScaleY + scaleH - y >= 0) {

// 缩放区域

return 'transform';

} else if (x - transformDelX >= 0 && y - transformDelY >= 0 &&

transformDelX + delW - x >= 0 && transformDelY + delH - y >= 0) {

// 删除区域

return 'del';

} else if (x - moveX >= 0 && y - moveY >= 0 &&

moveX + selectW - x >= 0 && moveY + selectH - y >= 0) {

// 移动区域

return 'move';

}

// 不在选择区域里面

return false;

},

/**

* 两点求角度

* @param {*} px1

* @param {*} py1

* @param {*} px2

* @param {*} py2

*/

_getAngle(px1, py1, px2, py2) {

const x = px2 - px1;

const y = py2 - py1;

const hypotenuse = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));

//斜边长度

const cos = x / hypotenuse;

const radian = Math.acos(cos);

const angle = 180 / (Math.PI / radian);

return angle;

},

/**

* 点选择一定角度之后的坐标

* @param {*} x

* @param {*} y

* @param {*} rotate 旋转的角度

*/

_getTransform(x, y, rotate) {

const angle = (Math.PI / 180) * (rotate);

const r = Math.sqrt(Math.pow((x - this.centerX), 2) + Math.pow((y - this.centerY), 2));

const a = Math.sin(angle) * r;

const b = Math.cos(angle) * r;

return {

x: this.centerX + b,

y: this.centerY + a,

};

},

/**

*

* @param {*} px 手指按下去的坐标

* @param {*} py 手指按下去的坐标

* @param {*} x 手指移动到的坐标

* @param {*} y 手指移动到的坐标

* @param {*} currentGraph 当前图层的信息

*/

transform(px, py, x, y, currentGraph) {

// 获取选择区域的宽度高度

if (this.type === 'text') {

this.ctx.setFontSize(this.fontSize);

}

const centerX = this.type === 'text' ? this.x : this.x + (this.w / 2);

const centerY = this.type === 'text' ? this.y : this.y + (this.h / 2);

const diffXBefore = px - centerX;

const diffYBefore = py - centerY;

const diffXAfter = x - centerX;

const diffYAfter = y - centerY;

const angleBefore = Math.atan2(diffYBefore, diffXBefore) / Math.PI * 180;

const angleAfter = Math.atan2(diffYAfter, diffXAfter) / Math.PI * 180;

// 旋转的角度

this.rotate = currentGraph.rotate + angleAfter - angleBefore;

const lineA = Math.sqrt(Math.pow((centerX - px), 2) + Math.pow((centerY - py), 2));

const lineB = Math.sqrt(Math.pow((centerX - x), 2) + Math.pow((centerY - y), 2));

if (this.type === 'image') {

const w = currentGraph.w + (lineB - lineA);

const h = currentGraph.h + (lineB - lineA);

this.w &#61; w <&#61; this.MIN_WIDTH ? this.MIN_WIDTH : w;

this.h &#61; h <&#61; this.MIN_WIDTH ? this.MIN_WIDTH : h;

if (w > this.MIN_WIDTH && h > this.MIN_WIDTH) {

// 放大 或 缩小

this.x &#61; currentGraph.x - (lineB - lineA) / 2;

this.y &#61; currentGraph.y - (lineB - lineA) / 2;

}

} else if (this.type &#61;&#61;&#61; &#39;text&#39;) {

const fontSize &#61; currentGraph.fontSize * ((lineB - lineA) / lineA &#43; 1);

this.fontSize &#61; fontSize <&#61; this.MIN_FONTSIZE ? this.MIN_FONTSIZE : fontSize;

}

},

/**

* 画圆角矩形

*/

_drawRadiusRect(x, y, w, h, r) {

const br &#61; r / 2;

this.ctx.beginPath();

this.ctx.moveTo(this.toPx(x &#43; br), this.toPx(y)); // 移动到左上角的点

this.ctx.lineTo(this.toPx(x &#43; w - br), this.toPx(y));

this.ctx.arcTo(this.toPx(x &#43; w), this.toPx(y), this.toPx(x &#43; w), this.toPx(y &#43; br), this.toPx(br));

this.ctx.lineTo(this.toPx(x &#43; w), this.toPx(y &#43; h - br));

this.ctx.arcTo(this.toPx(x &#43; w), this.toPx(y &#43; h), this.toPx(x &#43; w - br), this.toPx(y &#43; h), this.toPx(br));

this.ctx.lineTo(this.toPx(x &#43; br), this.toPx(y &#43; h));

this.ctx.arcTo(this.toPx(x), this.toPx(y &#43; h), this.toPx(x), this.toPx(y &#43; h - br), this.toPx(br));

this.ctx.lineTo(this.toPx(x), this.toPx(y &#43; br));

this.ctx.arcTo(this.toPx(x), this.toPx(y), this.toPx(x &#43; br), this.toPx(y), this.toPx(br));

},

toPx(rpx) {

return rpx * this.factor;

},

}

Component({

/**

* 组件的属性列表

*/

properties: {

graph: {

type: Object,

value: {},

observer: &#39;onGraphChange&#39;,

},

bgColor: {

type: String,

value: &#39;&#39;,

},

bgImage: {

type: String,

value: &#39;&#39;,

},

width: {

type: Number,

value: 750,

},

height: {

type: Number,

value: 750,

},

},

/**

* 组件的初始数据

*/

data: {

},

attached() {

const sysInfo &#61; wx.getSystemInfoSync();

const screenWidth &#61; sysInfo.screenWidth;

this.factor &#61; screenWidth / 750;

if (typeof this.drawArr &#61;&#61;&#61; &#39;undefined&#39;) {

this.drawArr &#61; [];

}

this.ctx &#61; wx.createCanvasContext(&#39;canvas-label&#39;, this);

this.draw();

},

/**

* 组件的方法列表

*/

methods: {

toPx(rpx) {

return rpx * this.factor;

},

onGraphChange(n, o) {

if (JSON.stringify(n) &#61;&#61;&#61; &#39;{}&#39;) return;

this.drawArr.push(new dragGraph(Object.assign({

x: 30,

y: 30,

}, n), this.ctx, this.factor));

this.draw();

},

draw() {

if (this.data.bgImage !&#61;&#61; &#39;&#39;) {

this.ctx.drawImage(this.data.bgImage, 0, 0, this.toPx(this.data.width), this.toPx(this.data.height));

}

if (this.data.bgColor !&#61;&#61; &#39;&#39;) {

this.ctx.save();

this.ctx.setFillStyle(this.data.bgColor);

this.ctx.fillRect(0, 0, this.toPx(this.data.width), this.toPx(this.data.height));

this.ctx.restore();

}

this.drawArr.forEach((item) &#61;> {

item.paint();

});

return new Promise((resolve) &#61;> {

this.ctx.draw(false, () &#61;> {

resolve();

});

});

},

start(e) {

const { x, y } &#61; e.touches[0];

this.tempGraphArr &#61; [];

this.drawArr && this.drawArr.forEach((item, index) &#61;> {

item.selected &#61; false;

const action &#61; item.isInGraph(x, y);

if (action) {

if (action &#61;&#61;&#61; &#39;del&#39;) {

this.drawArr.splice(index, 1);

this.ctx.clearRect(0, 0, this.toPx(this.data.width), this.toPx(this.data.height));

this.ctx.draw();

} else if (action &#61;&#61;&#61; &#39;transform&#39; || action &#61;&#61;&#61; &#39;move&#39;) {

item.action &#61; action;

this.tempGraphArr.push(item);

// 保存点击时的坐标

this.currentTouch &#61; { x, y };

}

}

});

// 保存点击时元素的信息

if (this.tempGraphArr.length > 0) {

const lastIndex &#61; this.tempGraphArr.length - 1;

this.tempGraphArr[lastIndex].selected &#61; true;

this.currentGraph &#61; Object.assign({}, this.tempGraphArr[lastIndex]);

}

this.draw();

},

move(e) {

const { x, y } &#61; e.touches[0];

if (this.tempGraphArr && this.tempGraphArr.length > 0) {

const currentGraph &#61; this.tempGraphArr[this.tempGraphArr.length - 1];

if (currentGraph.action &#61;&#61;&#61; &#39;move&#39;) {

currentGraph.x &#61; this.currentGraph.x &#43; (x - this.currentTouch.x);

currentGraph.y &#61; this.currentGraph.y &#43; (y - this.currentTouch.y);

} else if (currentGraph.action &#61;&#61;&#61; &#39;transform&#39;) {

currentGraph.transform(this.currentTouch.x, this.currentTouch.y, x, y, this.currentGraph);

}

this.draw();

}

},

end(e) {

this.tempGraphArr &#61; [];

},

export() {

return new Promise((resolve, reject) &#61;> {

this.drawArr &#61; this.drawArr.map((item) &#61;> {

item.selected &#61; false;

return item;

});

this.draw().then(() &#61;> {

wx.canvasToTempFilePath({

canvasId: &#39;canvas-label&#39;,

success: (res) &#61;> { resolve(res.tempFilePath); },

fail: (e) &#61;> { reject(e); },

}, this);

});

})

},

changColor(color) {

const selected &#61; this.drawArr.filter((item) &#61;> item.selected);

if (selected.length > 0) {

selected[0].color &#61; color;

}

this.draw();

},

changeBgColor(color) {

this.data.bgImage &#61; &#39;&#39;;

this.data.bgColor &#61; color;

this.draw();

},

changeBgImage(url) {

this.data.bgColor &#61; &#39;&#39;;

this.data.bgImage &#61; url;

this.draw();

}

}

})

2.index.wxss代码(canvas-drag)

/* components/canvas-drag/index.wxss */

.movable-label {

margin-top: 300rpx;

width: 750rpx;

height: 400rpx;

background: #eee;

}

.movable-block {

width: 120rpx;

height: 120rpx;

background: #ccc;

}

.movable-block .image-con {

width: 100%;

height: 100%;

}

3.index.wxml代码(canvas-drag)

disable-scroll&#61;"true"

bindtouchstart&#61;"start"

bindtouchmove&#61;"move"

bindtouchend&#61;"end"

style&#61;&#39;width: {{width}}rpx; height: {{height}}rpx;&#39;>

4.index.js逻辑代码(index)

a.部分的功能实现

import CanvasDrag from &#39;../../components/canvas-drag/canvas-drag&#39;;

Page({

data: {

graph: {},

},

/**

* 添加测试图片

*/

onAddTest() {

this.setData({

graph: {

w: 120,

h: 120,

type: &#39;image&#39;,

url: &#39;../../assets/images/test.jpg&#39;,

}

});

},

/**

* 添加图片

*/

onAddImage() {

wx.chooseImage({

success: (res) &#61;> {

this.setData({

graph: {

w: 200,

h: 200,

type: &#39;image&#39;,

url: res.tempFilePaths[0],

}

});

}

})

},

/**

* 添加文本

*/

onAddText() {

this.setData({

graph: {

type: &#39;text&#39;,

text: &#39;helloworld&#39;,

}

});

},

/**

* 导出图片

*/

onExport() {

CanvasDrag.export()

.then((filePath) &#61;> {

console.log(filePath);

wx.previewImage({

urls: [filePath]

})

})

.catch((e) &#61;> {

console.error(e);

})

},

/**

* 改变文字颜色

*/

onChangeColor() {

CanvasDrag.changFontColor(&#39;blue&#39;);

},

/**

* 改变背景颜色

*/

onChangeBgColor() {

CanvasDrag.changeBgColor(&#39;yellow&#39;);

},

/**

* 改变背景照片

*/

onChangeBgImage() {

CanvasDrag.changeBgImage(&#39;../../assets/images/test.jpg&#39;);

},

})

三、案例运行效果图

四、总结与备注

暂无

注&#xff1a;本文著作权归作者&#xff0c;由demo大师发表&#xff0c;拒绝转载&#xff0c;转载需要作者授权



推荐阅读
  • 小编给大家分享一下Vue3中如何提高开发效率,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获, ... [详细]
  • 视觉Transformer综述
    本文综述了视觉Transformer在计算机视觉领域的应用,从原始Transformer出发,详细介绍了其在图像分类、目标检测和图像分割等任务中的最新进展。文章不仅涵盖了基础的Transformer架构,还深入探讨了各类增强版Transformer模型的设计思路和技术细节。 ... [详细]
  • 本文详细探讨了Java中HashMap类的hash()方法的工作原理及其重要性,特别是在JDK 7版本中的实现。 ... [详细]
  • 深入解析Unity3D游戏开发中的音频播放技术
    在游戏开发中,音频播放是提升玩家沉浸感的关键因素之一。本文将探讨如何在Unity3D中高效地管理和播放不同类型的游戏音频,包括背景音乐和效果音效,并介绍实现这些功能的具体步骤。 ... [详细]
  • ASP.NET 进度条实现详解
    本文介绍了如何在ASP.NET中使用HTML和JavaScript创建一个动态更新的进度条,并通过Default.aspx页面进行展示。 ... [详细]
  • 利用Node.js实现PSD文件的高效切图
    本文介绍了如何通过Node.js及其psd2json模块,快速实现PSD文件的自动化切图过程,以适应项目中频繁的界面更新需求。此方法不仅提高了工作效率,还简化了从设计稿到实际应用的转换流程。 ... [详细]
  • 在1995年,Simon Plouffe 发现了一种特殊的求和方法来表示某些常数。两年后,Bailey 和 Borwein 在他们的论文中发表了这一发现,这种方法被命名为 Bailey-Borwein-Plouffe (BBP) 公式。该问题要求计算圆周率 π 的第 n 个十六进制数字。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
  • 入门指南:使用FastRPC技术连接Qualcomm Hexagon DSP
    本文旨在为初学者提供关于如何使用FastRPC技术连接Qualcomm Hexagon DSP的基础知识。FastRPC技术允许开发者在本地客户端实现远程调用,从而简化Hexagon DSP的开发和调试过程。 ... [详细]
  • importjava.io.*;importjava.util.*;publicclass五子棋游戏{staticintm1;staticintn1;staticfinalintS ... [详细]
  • 本文探讨了异步编程的发展历程,从最初的AJAX异步回调到现代的Promise、Generator+Co以及Async/Await等技术。文章详细分析了Promise的工作原理及其源码实现,帮助开发者更好地理解和使用这一重要工具。 ... [详细]
  • 本文探讨了如何通过优化 DOM 操作来提升 JavaScript 的性能,包括使用 `createElement` 函数、动画元素、理解重绘事件及处理鼠标滚动事件等关键主题。 ... [详细]
  • 本文详细介绍了C++中的构造函数,包括其定义、特点以及如何通过构造函数进行对象的初始化。此外,还探讨了转换构造函数的概念及其在不同情境下的应用,以及如何避免不必要的隐式类型转换。 ... [详细]
  • 解决Visual Studio Code中PHP Intelephense误报问题
    PHP作为一种高度灵活的编程语言,其代码结构可能导致Intelephense插件在某些情况下报告不必要的错误或警告。自1.3.3版本起,Intelephense引入了多个配置选项,允许用户根据具体的工作环境和编程风格调整这些诊断信息的显示。 ... [详细]
  • 高级缩放示例.就像谷歌地图一样.它仅缩放图块,但不缩放整个图像.因此,缩放的瓷砖占据了恒定的记忆,并且不会为大型缩放图像调整大小的图像.对于简化的缩放示例lookhere.在Win ... [详细]
author-avatar
新人缓冲
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有