网页截图方案总结2.0
之前总结了一些前端截图方案,其中有一个是手动调用canvas的api进行绘制,多次使用之后发现每次都要写很复杂的代码,所以就把主要逻辑封装了一下,改成了通过json配置文字和图片内容,虽然网上已经有一些了,但是都比较臃肿,有很多功能用不到,这里实现的是最轻量的。
// 兼容低版本浏览器
if (typeof Object.assign != 'function') {
Object.assign = function (target) {
'use strict';
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
target = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source != null) {
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
}
return target;
};
}
// 通过配置实现canvas绘制图片,callback可以拿到base64的图片
function drawCanvas(canvas, data, callback, callbackError) {
var defaultData = {
width: 750,
height: 10,
lineHeight: 1.5,
color: '#263238',
textAlign: 'left',
fontSize: 14,
fontFamily: '"PingFang SC",tahoma,arial,"helvetica neue","hiragino sans gb","microsoft yahei",sans-serif',
autoHeight: false,
};
var ctx = canvas.getContext('2d');
var newData = Object.assign({}, defaultData, data);
var currentY = 0,
loadElementLength = 0,
loadImgLength = 0,
elementLength = 0,
imgElementLength = 0;
// 设置canvas宽高
canvas.width = newData.width;
canvas.height = 2000;
ctx.fillStyle = newData.bgColor || '#fff';
ctx.fillRect(0, 0, newData.width, 2000);
// 指定宽高内绘制图片(background-size:cover方式)
var drawImgCover = function (img, contW, contH, startX, startY) {
if (img.width / img.height >= contW / contH) {
var dH = img.height;
var dW = Math.ceil(contW / contH * dH);
ctx.drawImage(img, (img.width - dW) / 2, 0, dW, img.height, startX, startY, contW, contH);
} else {
var dW = img.width;
var dH = Math.ceil(contH / contW * dW);
ctx.drawImage(img, 0, (img.height - dH) / 2, img.width, dH, startX, startY, contW, contH);
}
};
// 自动换行
var canvasTextAutoLine = function (str, initX, initY, width, height, lineHeight) {
var lineWidth = 0;
var lastSubStrIndex = 0;
var strLen = str.length;
for (var i = 0; i < strLen; i++) {
lineWidth += ctx.measureText(str[i]).width;
if (lineWidth > width) {
ctx.fillText(str.substring(lastSubStrIndex, i), initX, initY);
initY += lineHeight;
console.log(initY, height);
if (newData.autoHeight && initY >= height) {
currentY += lineHeight;
}
lineWidth = ctx.measureText(str[i]).width;
lastSubStrIndex = i;
}
if (i == str.length - 1) {
ctx.fillText(str.substring(lastSubStrIndex, i + 1), initX, initY);
}
}
};
// 绘制圆角并裁剪
var drawRoundedRect = function (x, y, width, height, r) {
ctx.save();
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + width, y, x + width, y + r, r);
ctx.arcTo(x + width, y + height, x + width - r, y + height, r);
ctx.arcTo(x, y + height, x, y + height - r, r);
ctx.arcTo(x, y, x + r, y, r);
ctx.closePath();
ctx.clip();
};
// 居中写字
var centerText = function (str, startY) {
var textW = ctx.measureText(str).width;
var x = (newData.width - textW) / 2;
ctx.fillText(str, x, startY);
};
// 居中图片
var centerImg = function (img, startY) {
var w = img.width;
var x = (newData.width - w) / 2;
ctx.drawImage(img, x, startY);
};
// 最大长度
var ellipsis = function (str, maxWidth) {
var strLen = str.length;
for (var i = 0; i < strLen; i++) {
if (ctx.measureText(str.substr(0, i + 1)).width > maxWidth) {
return `${str.substr(0, i)}...`;
}
}
return str;
};
// 导出
var output = function () {
// 获取可变部分图像
var contW = newData.width,
contH = newData.height;
if (newData.autoHeight) {
contH += currentY;
}
console.log(contH, currentY);
var main = ctx.getImageData(0, 0, contW, contH);
// 重新建空画布
canvas.width = contW;
canvas.height = contH;
ctx.clearRect(0, 0, contW, 2000); //清空
// 绘制可变部分图像
ctx.putImageData(main, 0, 0);
// 导出图片url
var dataUrl = canvas.toDataURL('image/png', 1);
setTimeout(function () {
callback && callback(dataUrl);
}, 100);
};
var checkOutput = function () {
loadElementLength++;
if (loadElementLength === elementLength) {
output();
}
};
// 加载图片
loadImage = function (imgArr, callback) {
imgArr.forEach(function (item) {
if (!item) return;
var Img = new Image();
Img.crossOrigin = "anonymous";
Img.src = item.content;
Img.onload = function () {
item.imgObj = this;
loadImgLength++;
if (loadImgLength === imgElementLength) {
callback && callback();
};
}
Img.onerror = function (err) {
callbackError && callbackError(err);
return false;
};
});
};
// 一个字一个字绘制
drawTextArr = function (contentArr, x, y, w, lineHeight, color, fontSize, fontfamily) {
if (!contentArr || !contentArr.length) return;
var lineWidth = 0;
var initX = x;
var initY = y;
contentArr.forEach(function (item) {
ctx.fillStyle = item.color || color;
var font = (item.fontSize || fontSize) + 'px ' + (item.fontfamily || fontfamily);
if (item.bold) {
font = item.bold + ' ' + ctx.font;
}
ctx.font = font;
var strtxt = item.content;
for (let i = 0; i < strtxt.length; i++) {
lineWidth += ctx.measureText(strtxt[i]).width;
if (lineWidth > w) {
initY += lineHeight;
if (newData.autoHeight) {
currentY += lineHeight;
}
lineWidth = ctx.measureText(strtxt[i]).width;
}
ctx.fillText(strtxt[i], initX + lineWidth - ctx.measureText(strtxt[i]).width, initY);
}
});
};
// 绘制图片
var drawImage = function (element) {
var x = element.x || 0,
y = element.y || 0,
w = element.width || 0,
h = element.height || 0;
if (currentY !== 0) {
y += currentY;
}
var Img = element.imgObj;
ctx.save();
if (element.autoHeight) {
var newH = Img.height * (w / Img.width);
if (newData.autoHeight) {
currentY += newH - h;
}
h = newH;
}
ctx.drawImage(Img, x, y, w, h);
ctx.restore();
if (element.r > 0) {
drawRoundedRect(x, y, w, h, element.r);
}
checkOutput();
};
// 绘制文本
var drawText = function (element) {
var fontfamily = element.fontfamily || newData.fontFamily,
fontSize = element.fontSize || newData.fontSize,
cont = element.content,
w = element.width || 0,
h = element.height || 0,
x = element.x || 0,
y = element.y || 0,
color = element.color || newData.color,
font = fontSize + 'px ' + fontfamily,
lh = element.lineHeight || newData.lineHeight;
var lineHeight = lh * fontSize;
if (element.bold) {
font = element.bold + ' ' + font;
}
if (currentY !== 0) {
y += currentY;
}
if (element.maxWidth) {
cont = ellipsis(cont, element.maxWidth);
}
y += fontSize + (lineHeight - fontSize) / 4;
ctx.beginPath();
ctx.fillStyle = color;
ctx.font = font;
if (typeof cont !== 'string') {
drawTextArr(cont, x, y, w, lineHeight, color, fontSize, fontfamily);
} else if (element.textAlign === 'center') {
centerText(cont, y);
} else if (element.autoHeight) {
canvasTextAutoLine(cont, x, y, w, y + h, lineHeight);
} else {
ctx.fillText(cont, x, y);
ctx.fill();
}
ctx.closePath();
checkOutput();
};
if (newData.elements && newData.elements.length) {
elementLength = newData.elements.length;
var imgArr = newData.elements.map(function (item) {
if (item.type === 'img') {
imgElementLength++;
return item;
}
});
loadImage(imgArr, function () {
newData.elements.forEach(function (item, index) {
if (item.type === 'img') {
item.imgObj = imgArr[index].imgObj;
drawImage(item);
} else {
drawText(item);
}
});
});
}
}
使用
function drawShare(canvas, data, callback, callbackError) {
var newData = {
width: 686,
height: 954,
color: '#263238',
bgColor: '#fff',
autoHeight: true,
elements: [
{
type: 'img',
x: 0,
y: 0,
width: 686,
height: 514,
autoHeight: true,
content: data.pic,
},
{
type: 'text',
width: 622,
height: 72,
x: 32,
y: 546,
fontSize: 48,
bold: 500,
autoHeight: true,
content: data.title,
},
{
type: 'text',
width: 622,
height: 112,
x: 32,
y: 634,
color: '#3C5A6E',
fontSize: 32,
autoHeight: true,
lineHeight: 1.75,
content: data.info,
},
{
type: 'img',
x: 32,
y: 776,
width: 622,
height: 2,
content: 'images/achievement/draw-share-line.png',
},
{
type: 'img',
x: 24,
y: 802,
width: 128,
height: 128,
content: data.qrcode,
},
{
type: 'text',
x: 176,
y: 814,
color: '#3C5A6E',
fontSize: 24,
content: '长按或扫描二维码',
},
{
type: 'text',
x: 176,
y: 850,
color: '#3C5A6E',
fontSize: 24,
content: '查看公益项目',
},
{
type: 'img',
x: 176,
y: 894,
width: 269,
height: 36,
content: 'images/achievement/draw-share-logo.png',
},
{
type: 'img',
x: 476,
y: 780,
width: 208,
height: 176,
content: 'images/achievement/draw-share-flower.png',
},
],
};
drawCanvas(canvas, newData, callback, callbackError);
}