小程序使用画布
微信小程序新版画布(Canvas 2D)是基于 HTML5 Canvas API 的现代化实现,相比旧版 Canvas 具有更好的性能和更丰富的功能。主要特点包括:
主要特性:
1, 性能优化
硬件加速渲染
更低的 CPU 占用
支持离屏渲染
2,API 现代化
支持标准的 Canvas API
更好的 TypeScript 支持
更直观的调用方式
3, 功能增强
完整的 2D 绘图 API
渐变、阴影、滤镜效果
文本测量和渲染
路径和形状操作
图像合成和变换
4,开发体验
更好的错误提示
更详细的文档
兼容 Web 标准
这是一个 JS 文件示例:
// pages/canvas/canvas.js
Page({
data: {
// 画布上下文
canvasContext: null,
// 画布尺寸
canvasWidth: 300,
canvasHeight: 300,
// 绘图状态
drawing: false,
lastX: 0,
lastY: 0,
// 画笔设置
brushColor: '#000000',
brushSize: 5,
brushOpacity: 1,
// 工具类型
currentTool: 'pen', // pen, eraser, line, rect, circle, text
// 图形属性
fillColor: '#ff0000',
strokeColor: '#000000',
lineWidth: 2,
// 文本设置
textContent: 'Hello Canvas',
textSize: 20,
textFont: 'Arial',
// 历史记录
history: [],
historyIndex: -1,
// 图层管理
layers: [],
currentLayer: 0,
// 滤镜效果
filters: {
brightness: 100,
contrast: 100,
saturation: 100,
blur: 0
},
// 画布状态
isSaved: false,
saveMessage: ''
},
onLoad() {
console.log('画布页面加载');
this.initCanvas();
},
onReady() {
// 页面渲染完成后初始化画布
setTimeout(() => {
this.setupCanvas();
}, 100);
},
onUnload() {
// 清理资源
if (this.data.canvasContext) {
this.data.canvasContext = null;
}
},
/**
* 初始化画布
*/
initCanvas() {
// 获取系统信息设置画布尺寸
wx.getSystemInfo({
success: (res) => {
const width = res.windowWidth * 0.9;
const height = width; // 正方形画布
this.setData({
canvasWidth: width,
canvasHeight: height
}, () => {
this.createCanvasContext();
});
}
});
},
/**
* 创建画布上下文
*/
createCanvasContext() {
return new Promise((resolve, reject) => {
const query = wx.createSelectorQuery();
query.select('#myCanvas')
.fields({ node: true, size: true })
.exec((res) => {
if (!res[0]) {
reject(new Error('画布元素未找到'));
return;
}
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
// 设置画布分辨率
const dpr = wx.getSystemInfoSync().pixelRatio;
canvas.width = this.data.canvasWidth * dpr;
canvas.height = this.data.canvasHeight * dpr;
ctx.scale(dpr, dpr);
this.setData({
canvasContext: ctx,
canvasNode: canvas
});
// 清空画布
this.clearCanvas();
resolve(ctx);
});
});
},
/**
* 设置画布
*/
async setupCanvas() {
try {
await this.createCanvasContext();
this.drawWelcomeScreen();
console.log('画布初始化成功');
} catch (error) {
console.error('画布初始化失败:', error);
wx.showToast({
title: '画布初始化失败',
icon: 'none'
});
}
},
/**
* 绘制欢迎界面
*/
drawWelcomeScreen() {
const ctx = this.data.canvasContext;
if (!ctx) return;
// 清空画布
this.clearCanvas();
// 绘制背景
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
// 绘制标题
ctx.fillStyle = '#333333';
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'center';
ctx.fillText('欢迎使用画板', this.data.canvasWidth / 2, 50);
// 绘制说明文字
ctx.font = '14px Arial';
ctx.fillText('请选择工具开始绘画', this.data.canvasWidth / 2, 80);
// 绘制示例图形
this.drawSampleShapes();
},
/**
* 绘制示例图形
*/
drawSampleShapes() {
const ctx = this.data.canvasContext;
const centerX = this.data.canvasWidth / 2;
const centerY = this.data.canvasHeight / 2;
// 绘制圆形
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(centerX - 60, centerY, 30, 0, Math.PI * 2);
ctx.fill();
// 绘制矩形
ctx.fillStyle = '#4ecdc4';
ctx.fillRect(centerX - 15, centerY - 30, 60, 60);
// 绘制三角形
ctx.fillStyle = '#45b7d1';
ctx.beginPath();
ctx.moveTo(centerX + 45, centerY - 30);
ctx.lineTo(centerX + 75, centerY + 30);
ctx.lineTo(centerX + 15, centerY + 30);
ctx.closePath();
ctx.fill();
this.saveState();
},
/**
* 清空画布
*/
clearCanvas() {
const ctx = this.data.canvasContext;
if (!ctx) return;
ctx.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
this.saveState();
},
/**
* 保存绘图状态
*/
saveState() {
if (!this.data.canvasNode) return;
// 获取画布图像数据
const canvas = this.data.canvasNode;
wx.canvasGetImageData({
canvasId: 'myCanvas',
x: 0,
y: 0,
width: this.data.canvasWidth,
height: this.data.canvasHeight,
success: (res) => {
const state = {
imageData: res,
timestamp: Date.now()
};
// 保存到历史记录
const newHistory = this.data.history.slice(0, this.data.historyIndex + 1);
newHistory.push(state);
this.setData({
history: newHistory,
historyIndex: newHistory.length - 1
});
}
});
},
/**
* 撤销操作
*/
undo() {
if (this.data.historyIndex <= 0) {
wx.showToast({
title: '无法继续撤销',
icon: 'none'
});
return;
}
this.setData({
historyIndex: this.data.historyIndex - 1
});
this.restoreState(this.data.historyIndex);
},
/**
* 重做操作
*/
redo() {
if (this.data.historyIndex >= this.data.history.length - 1) {
wx.showToast({
title: '无法继续重做',
icon: 'none'
});
return;
}
this.setData({
historyIndex: this.data.historyIndex + 1
});
this.restoreState(this.data.historyIndex);
},
/**
* 恢复状态
*/
restoreState(index) {
if (index < 0 || index >= this.data.history.length) return;
const state = this.data.history[index];
wx.canvasPutImageData({
canvasId: 'myCanvas',
x: 0,
y: 0,
width: this.data.canvasWidth,
height: this.data.canvasHeight,
data: state.imageData.data,
success: () => {
console.log('状态恢复成功');
}
});
},
/**
* 触摸开始事件
*/
onTouchStart(e) {
if (!this.data.canvasContext) return;
const touch = e.touches[0];
const x = touch.x;
const y = touch.y;
this.setData({
drawing: true,
lastX: x,
lastY: y
});
// 根据工具类型执行不同操作
switch (this.data.currentTool) {
case 'pen':
case 'eraser':
this.startDrawing(x, y);
break;
case 'line':
this.startLine(x, y);
break;
case 'rect':
this.startRect(x, y);
break;
case 'circle':
this.startCircle(x, y);
break;
case 'text':
this.startTextInput(x, y);
break;
}
},
/**
* 触摸移动事件
*/
onTouchMove(e) {
if (!this.data.drawing || !this.data.canvasContext) return;
const touch = e.touches[0];
const x = touch.x;
const y = touch.y;
switch (this.data.currentTool) {
case 'pen':
this.drawLine(this.data.lastX, this.data.lastY, x, y);
break;
case 'eraser':
this.erase(x, y);
break;
case 'line':
this.drawTempLine(x, y);
break;
case 'rect':
this.drawTempRect(x, y);
break;
case 'circle':
this.drawTempCircle(x, y);
break;
}
this.setData({
lastX: x,
lastY: y
});
},
/**
* 触摸结束事件
*/
onTouchEnd(e) {
if (!this.data.drawing) return;
const touch = e.changedTouches[0];
const x = touch.x;
const y = touch.y;
switch (this.data.currentTool) {
case 'line':
this.finishLine(x, y);
break;
case 'rect':
this.finishRect(x, y);
break;
case 'circle':
this.finishCircle(x, y);
break;
}
this.setData({
drawing: false
});
// 保存状态
this.saveState();
},
/**
* 开始绘制
*/
startDrawing(x, y) {
const ctx = this.data.canvasContext;
ctx.beginPath();
ctx.moveTo(x, y);
},
/**
* 绘制线条
*/
drawLine(x1, y1, x2, y2) {
const ctx = this.data.canvasContext;
if (this.data.currentTool === 'pen') {
ctx.strokeStyle = this.data.brushColor;
ctx.lineWidth = this.data.brushSize;
ctx.globalAlpha = this.data.brushOpacity;
} else {
ctx.strokeStyle = '#ffffff'; // 橡皮擦用白色
ctx.lineWidth = this.data.brushSize * 2;
ctx.globalAlpha = 1;
}
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
},
/**
* 橡皮擦功能
*/
erase(x, y) {
const ctx = this.data.canvasContext;
// 使用目的地输出模式实现橡皮擦效果
ctx.globalCompositeOperation = 'destination-out';
ctx.strokeStyle = 'rgba(0,0,0,1)';
ctx.lineWidth = this.data.brushSize * 2;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(this.data.lastX, this.data.lastY);
ctx.lineTo(x, y);
ctx.stroke();
// 恢复合成模式
ctx.globalCompositeOperation = 'source-over';
},
/**
* 开始绘制直线
*/
startLine(x, y) {
this.setData({
startX: x,
startY: y
});
},
/**
* 绘制临时直线(预览)
*/
drawTempLine(x, y) {
// 保存当前状态
this.restoreState(this.data.historyIndex);
const ctx = this.data.canvasContext;
ctx.strokeStyle = this.data.strokeColor;
ctx.lineWidth = this.data.lineWidth;
ctx.setLineDash([5, 5]); // 虚线预览
ctx.beginPath();
ctx.moveTo(this.data.startX, this.data.startY);
ctx.lineTo(x, y);
ctx.stroke();
ctx.setLineDash([]); // 恢复实线
},
/**
* 完成直线绘制
*/
finishLine(x, y) {
const ctx = this.data.canvasContext;
ctx.strokeStyle = this.data.strokeColor;
ctx.lineWidth = this.data.lineWidth;
ctx.setLineDash([]);
ctx.beginPath();
ctx.moveTo(this.data.startX, this.data.startY);
ctx.lineTo(x, y);
ctx.stroke();
},
/**
* 开始绘制矩形
*/
startRect(x, y) {
this.setData({
startX: x,
startY: y
});
},
/**
* 绘制临时矩形
*/
drawTempRect(x, y) {
this.restoreState(this.data.historyIndex);
const ctx = this.data.canvasContext;
ctx.strokeStyle = this.data.strokeColor;
ctx.lineWidth = this.data.lineWidth;
ctx.setLineDash([5, 5]);
const width = x - this.data.startX;
const height = y - this.data.startY;
ctx.strokeRect(this.data.startX, this.data.startY, width, height);
ctx.setLineDash([]);
},
/**
* 完成矩形绘制
*/
finishRect(x, y) {
const ctx = this.data.canvasContext;
ctx.strokeStyle = this.data.strokeColor;
ctx.fillStyle = this.data.fillColor;
ctx.lineWidth = this.data.lineWidth;
const width = x - this.data.startX;
const height = y - this.data.startY;
ctx.fillRect(this.data.startX, this.data.startY, width, height);
ctx.strokeRect(this.data.startX, this.data.startY, width, height);
},
/**
* 开始绘制圆形
*/
startCircle(x, y) {
this.setData({
startX: x,
startY: y
});
},
/**
* 绘制临时圆形
*/
drawTempCircle(x, y) {
this.restoreState(this.data.historyIndex);
const ctx = this.data.canvasContext;
ctx.strokeStyle = this.data.strokeColor;
ctx.lineWidth = this.data.lineWidth;
ctx.setLineDash([5, 5]);
const radius = Math.sqrt(
Math.pow(x - this.data.startX, 2) + Math.pow(y - this.data.startY, 2)
);
ctx.beginPath();
ctx.arc(this.data.startX, this.data.startY, radius, 0, Math.PI * 2);
ctx.stroke();
ctx.setLineDash([]);
},
/**
* 完成圆形绘制
*/
finishCircle(x, y) {
const ctx = this.data.canvasContext;
ctx.strokeStyle = this.data.strokeColor;
ctx.fillStyle = this.data.fillColor;
ctx.lineWidth = this.data.lineWidth;
const radius = Math.sqrt(
Math.pow(x - this.data.startX, 2) + Math.pow(y - this.data.startY, 2)
);
ctx.beginPath();
ctx.arc(this.data.startX, this.data.startY, radius, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
},
/**
* 开始文本输入
*/
startTextInput(x, y) {
this.setData({
textX: x,
textY: y,
showTextInput: true
});
},
/**
* 完成文本输入
*/
finishTextInput() {
if (!this.data.textContent.trim()) return;
const ctx = this.data.canvasContext;
ctx.fillStyle = this.data.brushColor;
ctx.font = `${this.data.textSize}px ${this.data.textFont}`;
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText(this.data.textContent, this.data.textX, this.data.textY);
this.setData({
showTextInput: false,
textContent: 'Hello Canvas'
});
this.saveState();
},
/**
* 改变画笔颜色
*/
changeBrushColor(color) {
this.setData({
brushColor: color
});
},
/**
* 改变画笔大小
*/
changeBrushSize(size) {
this.setData({
brushSize: size
});
},
/**
* 改变画笔透明度
*/
changeBrushOpacity(opacity) {
this.setData({
brushOpacity: opacity / 100
});
},
/**
* 选择工具
*/
selectTool(tool) {
this.setData({
currentTool: tool
});
const toolNames = {
'pen': '画笔',
'eraser': '橡皮擦',
'line': '直线',
'rect': '矩形',
'circle': '圆形',
'text': '文本'
};
wx.showToast({
title: `已选择${toolNames[tool]}`,
icon: 'none',
duration: 1000
});
},
/**
* 应用滤镜
*/
applyFilter() {
const ctx = this.data.canvasContext;
const filterString = this.generateFilterString();
ctx.filter = filterString;
this.redrawCanvas();
ctx.filter = 'none'; // 重置滤镜
this.saveState();
},
/**
* 生成滤镜字符串
*/
generateFilterString() {
const f = this.data.filters;
return `brightness(${f.brightness}%) contrast(${f.contrast}%) saturate(${f.saturation}%) blur(${f.blur}px)`;
},
/**
* 改变滤镜值
*/
changeFilter(type, value) {
const newFilters = { ...this.data.filters };
newFilters[type] = value;
this.setData({
filters: newFilters
});
this.applyFilter();
},
/**
* 重绘画布
*/
redrawCanvas() {
// 重新绘制当前状态
this.restoreState(this.data.historyIndex);
},
/**
* 保存画布到相册
*/
saveToPhotosAlbum() {
return new Promise((resolve, reject) => {
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
wx.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: () => {
this.setData({
isSaved: true,
saveMessage: '图片已保存到相册'
});
wx.showToast({
title: '保存成功',
icon: 'success'
});
resolve();
},
fail: (err) => {
this.handleSaveError(err);
reject(err);
}
});
},
fail: (err) => {
console.error('生成临时文件失败', err);
reject(err);
}
});
});
},
/**
* 保存为文件
*/
saveToFile() {
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
success: (res) => {
wx.saveFile({
tempFilePath: res.tempFilePath,
success: (saveRes) {
this.setData({
isSaved: true,
saveMessage: `文件已保存: ${saveRes.savedFilePath}`
});
wx.showToast({
title: '文件保存成功',
icon: 'success'
});
},
fail: this.handleSaveError
});
}
});
},
/**
* 处理保存错误
*/
handleSaveError(err) {
console.error('保存失败', err);
let errorMsg = '保存失败';
if (err.errMsg.includes('auth deny')) {
errorMsg = '没有相册访问权限';
}
this.setData({
saveMessage: errorMsg
});
wx.showToast({
title: errorMsg,
icon: 'none'
});
},
/**
* 导出画布数据
*/
exportCanvasData() {
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
fileType: 'png',
quality: 1,
success: (res) => {
// 这里可以将临时文件路径上传到服务器或进行其他处理
console.log('画布导出成功', res.tempFilePath);
wx.showToast({
title: '导出成功',
icon: 'success'
});
// 可以在这里添加分享功能
this.shareCanvasImage(res.tempFilePath);
}
});
},
/**
* 分享画布图片
*/
shareCanvasImage(tempFilePath) {
wx.share({
filePath: tempFilePath,
success: () => {
console.log('分享成功');
},
fail: (err) => {
console.error('分享失败', err);
}
});
},
/**
* 重置画布
*/
resetCanvas() {
wx.showModal({
title: '确认重置',
content: '确定要清空画布吗?',
success: (res) => {
if (res.confirm) {
this.clearCanvas();
this.drawWelcomeScreen();
this.setData({
history: [],
historyIndex: -1
});
}
}
});
},
/**
* 绘制渐变背景
*/
drawGradientBackground() {
const ctx = this.data.canvasContext;
const gradient = ctx.createLinearGradient(0, 0, this.data.canvasWidth, this.data.canvasHeight);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
this.saveState();
},
/**
* 绘制网格背景
*/
drawGridBackground() {
const ctx = this.data.canvasContext;
const size = 20;
ctx.strokeStyle = '#e0e0e0';
ctx.lineWidth = 0.5;
// 绘制垂直线
for (let x = 0; x <= this.data.canvasWidth; x += size) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, this.data.canvasHeight);
ctx.stroke();
}
// 绘制水平线
for (let y = 0; y <= this.data.canvasHeight; y += size) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(this.data.canvasWidth, y);
ctx.stroke();
}
this.saveState();
},
/**
* 添加水印
*/
addWatermark() {
const ctx = this.data.canvasContext;
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.font = '12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 在整个画布上添加水印文字
for (let y = 20; y < this.data.canvasHeight; y += 50) {
for (let x = 20; x < this.data.canvasWidth; x += 100) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(-0.3); // 倾斜角度
ctx.fillText('My Canvas', 0, 0);
ctx.restore();
}
}
this.saveState();
},
/**
* 获取画布统计信息
*/
getCanvasStats() {
if (!this.data.history.length) return null;
return {
totalOperations: this.data.history.length,
currentState: this.data.historyIndex + 1,
canvasSize: `${this.data.canvasWidth} × ${this.data.canvasHeight}`,
lastSaved: this.data.isSaved ? '已保存' : '未保存'
};
},
/**
* 显示画布信息
*/
showCanvasInfo() {
const stats = this.getCanvasStats();
if (!stats) return;
const info = `画布信息:
操作次数: ${stats.totalOperations}
当前状态: ${stats.currentState}
画布尺寸: ${stats.canvasSize}
保存状态: ${stats.lastSaved}`;
wx.showModal({
title: '画布信息',
content: info,
showCancel: false
});
},
/**
* 页面分享
*/
onShareAppMessage() {
return {
title: '我的画布作品',
path: '/pages/canvas/canvas'
};
}
});
主要功能说明
- 画布初始化
createCanvasContext(): 创建画布上下文
setupCanvas(): 设置画布参数
自动适配屏幕尺寸和分辨率
- 绘图工具
pen: 画笔工具,支持颜色、大小、透明度设置
eraser: 橡皮擦工具
line: 直线绘制工具
rect: 矩形绘制工具
circle: 圆形绘制工具
text: 文本输入工具
- 绘图控制
onTouchStart(): 触摸开始事件处理
onTouchMove(): 触摸移动事件处理
onTouchEnd(): 触摸结束事件处理
实时预览和最终绘制分离
- 历史记录管理
saveState(): 保存绘图状态
undo(): 撤销操作
redo(): 重做操作
restoreState(): 恢复特定状态
- 滤镜效果
brightness: 亮度调整
contrast: 对比度调整
saturation: 饱和度调整
blur: 模糊效果
实时滤镜应用
- 文件操作
saveToPhotosAlbum(): 保存到相册
saveToFile(): 保存为文件
exportCanvasData(): 导出画布数据
完善的错误处理
- 高级功能
渐变背景绘制
网格背景
水印添加
画布信息统计
分享功能
- 工具函数
颜色管理
尺寸调整
透明度控制
文本渲染
这个示例展示了新版画布的完整功能,包括基础绘图、高级特效、文件操作等,可以直接在小程序项目中使用。新版画布 API 更加现代化,性能更好,适合开发复杂的绘图应用。
通关密语:画布