前言
工作中遇到了需要将统计模块的图表下载的需求,查阅资料后发现直接把 canvas 转为图片即可实现,但是下载的图片除了图表中的线条外背景颜色是透明的,经过后续的改进能自定义下载图片的背景颜色以及背景图片,并扩展了多个 canvas 合并下载功能。本文为记录此需求的实现方式。
前置知识点
toDataURL()
HTMLCanvasElement.toDataURL() 方法用于将canvas中的图像内容转换为 base64 格式图片,默认为 PNG 格式。
可用于将图片转成 base64 格式
let canvas = document.getElementsByTagName('canvas')[0];
/**
* @param {String} type:图片类型
* @param {Number} encoderOptions:图片质量 0-1,默认值:0.92
* @return {String} base64格式图片数据
*/
canvas.toDataURL(type, encoderOptions);
drawImage()
CanvasRenderingContext2D.drawImage() 用于在 canvas 上绘制图像。
三种调用语法:
- 绘制原图:
context.drawImage(image, x, y) - 绘制指定大小的原图:
context.drawImage(img, dx, dy, dWidth, dHeight) - 绘制裁剪后的图片:
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
let canvas = document.getElementsByTagName('canvas')[0];
let context = canvas.getContext('2d');
/**
* @param {ImageSource} image:图像源
* 例:CSSImageValue || HTMLImageElement || SVGImageElement || HTMLVideoElement || HTMLCanvasElement || ImageBitmap || offscreenCanvas
* @param {Number} sx:开始裁剪图像的 x 坐标位置
* @param {Number} sy:开始裁剪图像的 y 坐标位置
* @param {Number} sWidth:裁剪出的图像宽度
* @param {Number} sHeight:裁剪出的图像高度
* @param {Number} dx:在画布上放置图像的 x 坐标位置
* @param {Number} dy:在画布上放置图像的 y 坐标位置
* @param {Number} dWidth:在画布上绘制图像宽度
* @param {Number} dHeight:在画布上绘制图像高度
*/
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
getImageData()
CanvasRenderingContext2D.getImageData() 返回 ImageData 对象,包含 canvas 区域类的像素数据。
let canvas = document.getElementsByTagName('canvas')[0];
let context = canvas.getContext('2d');
/**
* @param {Number} sx:将要提取图像数据矩形区域左上角x坐标
* @param {Number} sy:将要提取图像数据矩形区域左上角y坐标
* @param {Number} sw:矩形区域宽度
* @param {Number} sh:矩形区域高度
* @return {ImageData} canvas像素数据
*/
context.getImageData(sx, sy, sw, sh);
ImageData.data:一维数组,包含RGBA顺序的数据,0~255
ImageData.height:使用像素描述 ImageData 的实际高度
ImageData.width:使用像素描述 ImageData 的实际宽度
putImageData()
CanvasRenderingContext2D.putImageData() 将 ImageData 对象绘制到 cavas 中,如果提供一个绘制过的 canvas,则只绘制该 canvas 的像素
let canvas = document.getElementsByTagName('canvas')[0];
let context = canvas.getContext('2d');
/**
* @param {iamgeData} iamgeData:包含像素值的数组对象
* @param {Number} dx:x轴方向上的偏移量
* @param {Number} dy:y轴方向上的偏移量
*/
context.putImageData(iamgeData, dx, dy);
还有一些可选参数,详情 CanvasRenderingContext2D.putImageData() - Web API 接口参考 | MDN (mozilla.org)
globalCompositeOperation
CanvasRenderingContext2D.globalCompositeOperation 绘制新形状时合成操作的类型,默认值为 'source-over'
具体如何执行合成操作,可查看 CanvasRenderingContext2D.globalCompositeOperation - Web API 接口参考 | MDN (mozilla.org)
Canvas转图片并添加背景
添加背景颜色
/**
* canvas 增加背景颜色并生成图片
* @param {element} cav:canvas元素
* @param {String} backgroundColor:背景颜色
* @return {String} imageData:base64格式图片数据
*/
function canvasToImageByBackgroundColor(cav, backgroundColor) {
// 缓存 canvas 宽高
const height = cav.height;
const width = cav.width;
const context = cav.getContext('2d');
// 获取 canvas 上的像素信息
const oldImage = context.getImageData(0, 0, height, width);
// 保存 canvas 绘制新形状时合成操作的类型
const compositeOperation = context.globalCompositeOperation;
if (backgroundColor) {
// destination-over:在原有图形后面绘制新的图形
context.globalCompositeOperation = 'destination-over';
// 绘制背景颜色
context.fillStyle = backgroundColor;
context.fillRect(0, 0, width, height);
}
// 获取 canvas 中图像内容,格式为 base64
let imageData = cav.toDataUrl('image/png');
if (backgroundColor) {
// 擦除 canvas 中的内容
context.clearRect(0, 0, width, height);
// 还原增加背景色之前的 canvas
context.putImageData(oldImage, 0, 0);
context.globalCompositeOperation = compositeOperation;
}
return imageData;
}
添加背景图片
/**
* canvas 增加背景图片并生成图片
* @param {element} cav:canvas元素image
* @param {String} image:背景图片
* @return {String} imageData:base64格式图片数据
*/
function canvasToImageByBackgroundImage(cav, image) {
const height = cav.height;
const width = cav.width;
let imageEle = document.createElement('image');
imageEle.src = image;
let newCanvas = document.createElement('canvas');
let newContext = newCanvas.getContext('2d');
newCanvas.height = height;
newCanvas.width = width;
newContext.drawImage(imageEle, 0, 0, width, height);
newContext.drawImage(cav, 0, 0);
return newCanvas.toDataURL('image/png');
}
多个Canvas合并
/**
* 多个 canvas 合并为一个
* @param {HTMLCollection} canvasList:canvas元素列表
* @param {String} orientation:'row' || 'columns' 排序方式
* @return {HTMLElement} 合并后的 canvas
*/
function mergeCanvasList(canvasList, orientation = 'row') {
if(!canvasList || !canvasList.length) {
return null;
}
// 创建合并后的canvas
let totalCanvas = document.createElement('canvas');
let totalContext = totalCancas.getContext('2D');
// 初始化canvas宽高
totalCanvas.width = canvasList[0].width;
totalCanvas.height = canvasList[0].height;
for (let index = 0; index < canvasList.length; index++) {
const element = canvasList[index];
let elementWidth = element.clientWidth;
let elementHeight = element.clientHeight;
if (index === 0) {
totalContext.drawImage(element, 0, 0, elementWidth, elementHeight);
} else {
let width = totalCanvas.width;
let height = totalCanvas.height;
// 保存已绘制的图像,防止改变canvas宽高后canvas重新绘制导致丢失之前绘制内容
let canvasData = totalContext.getImageData(0, 0, width, height);
if (orientation === 'row') {
// 加宽 canvas
totalCanvas.width = width + elementWidth;
// 判断绘制的图像高度是否大于canvas高度,是则加高
if (elementHeight > height) {
totalCanvas.height = elementHeight;
}
// 将之前绘制的图像重新绘制到canvas
context.putImageData(canvasData, 0, 0);
context.drawIamge(element, width, 0, elementWidth, elementHeight)
} else {
// 加高 canvas
totalCanvas.height = height + elementHeight;
// 判断绘制的图像宽度是否大于canvas宽度,是则加宽
if (elementWidth > width) {
totalCanvas.width = elementWidth;
}
// 将之前绘制的图像重新绘制到canvas
context.putImageData(canvasData, 0, 0);
context.drawIamge(element, 0, height, elementWidth, elementHeight)
}
}
}
return totalCanvas
}
图片下载
function tagAToDownload({url, title = '', target = '_blank'} = {}) {
let tagA = document.createElement('a');
tagA.setAttribute('href', url);
tagA.setAttribute('download', title);
tagA.setAttribute('target', target);
document.body.appendChild(tagA);
tagA.click();
document.body.removeChild(tagA);
}
// tagAToDownload({url: canvasToImageByBackgroundColor(CanvasList, '#fff'), title: '下载文件名称'});
// tagAToDownload({url: canvasToImageByBackgroundImage(Canvas, imageUrl), title: '下载文件名称'});
// tagAToDownload({url: mergeCanvasList([CanvasList], 'row'), title: '下载文件名称'});
结语
感谢各位能看到这里!如果有所收获就点个赞再走吧,如果有更简单的实现方式或是其他的需求欢迎在评论区交流。