这些天在做一个海报图自动生成的功能,在这里用到了html2canvas插件的截图功能,但是呢在看htmlcanvas文档的时候作者特别标注:background-cli:text暂时不支持, 不支持这个的话,颜色渐变就无法去使用,截出来就是一团,可把我难受坏了,我找遍了全网了快终于在GitHub一个评论上找到了一个当时改这个问题的时候的作者,但是哦,他改变的只是编译之后的html2canvas.js的代码,大约都四年以前了,现在这个插件早就更新换代,用的ts,如果大家有兴趣的话可以去看一下:https://github.com/kaykie/html2canvas/commits/a6e9547ba9e3f0b21218bb1e3b988c0ae5fdfd70 (有html2canvas.js代码)
不说废话了,在这几天的奋战,跟着公司的大佬一起把这个改过之后的js还原成ts文件,在进行编译之后就可以用了.上代码:
我引用的版本是: 1.0.0-rc.7
修改之后html2canvas的ts代码
大家根据我改的部分去更改可以了
首先在 canvas-renderer.ts文件找到: xfy我用来标注我更改的
async renderTextNode(text: TextContainer, styles: CSSParsedDeclaration) {
const [font, fontFamily, fontSize] = this.createFontStyle(styles);
/` xfy `/
let adorWidth = 0;
for (let xnwi = 0; xnwi < text.textBounds.length; xnwi++) {
adorWidth += text.textBounds[xnwi].bounds.width;
}
const adorLeft = text.textBounds[0].bounds.left;
let adorTop = text.textBounds[0].bounds.top;
let adorHeight = text.textBounds[0].bounds.height;
this.ctx.font = font;
text.textBounds.forEach(text => {
/` xfy`/
const bgImage = styles.backgroundImage[0];
if (styles.backgroundClip[0] == BACKGROUND_CLIP.TEXT && isLinearGradient(bgImage)) {
if (styles.backgroundImage[0] === undefined) {
this.ctx.fillStyle = asString(styles.backgroundColor);
} else {
const [lineLength, x0, x1, y0, y1] = calculateGradientDirection(
bgImage.angle,
adorWidth,
adorHeight
);
const canvas = document.createElement('canvas');
canvas.width = adorWidth;
canvas.height = adorHeight;
const xctx = canvas.getContext('2d') as CanvasRenderingContext2D;
const gradient = xctx.createLinearGradient(
adorLeft + x0,
adorTop + y0,
adorLeft + x1,
adorTop + y1
);
processColorStops(bgImage.stops, lineLength).forEach(colorStop =>
gradient.addColorStop(colorStop.stop, asString(colorStop.color))
);
this.ctx.fillStyle = gradient;
}
} else {
this.ctx.fillStyle = asString(styles.color);
}
this.renderTextWithLetterSpacing(text, styles.letterSpacing);
const textShadows: TextShadow = styles.textShadow;
if (textShadows.length && text.text.trim().length) {
textShadows
.slice(0)
.reverse()
.forEach(textShadow => {
this.ctx.shadowColor = asString(textShadow.color);
this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
this.ctx.shadowBlur = textShadow.blur.number;
this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
});
this.ctx.shadowColor = '';
this.ctx.shadowOffsetX = 0;
this.ctx.shadowOffsetY = 0;
this.ctx.shadowBlur = 0;
}
if (styles.textDecorationLine.length) {
this.ctx.fillStyle = asString(styles.textDecorationColor || styles.color);
styles.textDecorationLine.forEach(textDecorationLine => {
switch (textDecorationLine) {
case TEXT_DECORATION_LINE.UNDERLINE:
// Draws a line at the baseline of the font
// TODO As some browsers display the line as more than 1px if the font-size is big,
// need to take that into account both in position and size
const {baseline} = this.fontMetrics.getMetrics(fontFamily, fontSize);
this.ctx.fillRect(
text.bounds.left,
Math.round(text.bounds.top + baseline),
text.bounds.width,
1
);
break;
case TEXT_DECORATION_LINE.OVERLINE:
this.ctx.fillRect(text.bounds.left, Math.round(text.bounds.top), text.bounds.width, 1);
break;
case TEXT_DECORATION_LINE.LINE_THROUGH:
// TODO try and find exact position for line-through
const {middle} = this.fontMetrics.getMetrics(fontFamily, fontSize);
this.ctx.fillRect(
text.bounds.left,
Math.ceil(text.bounds.top + middle),
text.bounds.width,
1
);
break;
}
});
}
});
}
renderReplacedElement(
container: ReplacedElementContainer,
curves: BoundCurves,
image: HTMLImageElement | HTMLCanvasElement
) {
if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
const bounds = contentBox(container);
/` xfy `/
let newWidth: number;
let newHeight: number;
let newX = bounds.left;
let newY = bounds.top;
if (container.intrinsicWidth / bounds.width < container.intrinsicHeight / bounds.height) {
newWidth = bounds.width;
newHeight = container.intrinsicHeight * (bounds.width / container.intrinsicWidth);
newY = bounds.top + (bounds.height - newHeight) / 2;
} else {
newWidth = container.intrinsicWidth * (bounds.height / container.intrinsicHeight);
newHeight = bounds.height;
newX = bounds.left + (bounds.width - newWidth) / 2;
}
const path = calculatePaddingBoxPath(curves);
this.path(path);
this.ctx.save();
this.ctx.clip();
this.ctx.drawImage(
image,
0,
0,
container.intrinsicWidth,
container.intrinsicHeight,
/` xfy `/
newX,
newY,
newWidth,
newHeight
);
this.ctx.restore();
}
}
async renderBackgroundImage(container: ElementContainer) {
let index = container.styles.backgroundImage.length - 1;
for (const backgroundImage of container.styles.backgroundImage.slice(0).reverse()) {
if (backgroundImage.type === CSSImageType.URL) {
let image;
const url = (backgroundImage as CSSURLImage).url;
try {
image = await this.options.cache.match(url);
} catch (e) {
Logger.getInstance(this.options.id).error(`Error loading background-image ${url}`);
}
if (image) {
const [path, x, y, width, height] = calculateBackgroundRendering(container, index, [
image.width,
image.height,
image.width / image.height
]);
const pattern = this.ctx.createPattern(
this.resizeImage(image, width, height),
'repeat'
) as CanvasPattern;
this.renderRepeat(path, pattern, x, y);
}
//xfy 记得这点是else if
} else if (container.styles.backgroundClip[0] == BACKGROUND_CLIP.TEXT) {
const [path, x, y, width, height] = calculateBackgroundRendering(container, index, [
null,
null,
null
]);
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
ctx.globalAlpha = 0;
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, width, height);
if (width > 0 && height > 0) {
const pattern = this.ctx.createPattern(canvas, 'repeat') as CanvasPattern;
this.renderRepeat(path, pattern, x, y);
}
}
else if (isLinearGradient(backgroundImage)) {
const [path, x, y, width, height] = calculateBackgroundRendering(container, index, [null, null, null]);
const [lineLength, x0, x1, y0, y1] = calculateGradientDirection(backgroundImage.angle, width, height);
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
const gradient = ctx.createLinearGradient(x0, y0, x1, y1);
processColorStops(backgroundImage.stops, lineLength).forEach(colorStop =>
gradient.addColorStop(colorStop.stop, asString(colorStop.color))
);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
if (width > 0 && height > 0) {
const pattern = this.ctx.createPattern(canvas, 'repeat') as CanvasPattern;
this.renderRepeat(path, pattern, x, y);
}
} else if (isRadialGradient(backgroundImage)) {
const [path, left, top, width, height] = calculateBackgroundRendering(container, index, [
null,
null,
null
]);
const position = backgroundImage.position.length === 0 ? [FIFTY_PERCENT] : backgroundImage.position;
const x = getAbsoluteValue(position[0], width);
const y = getAbsoluteValue(position[position.length - 1], height);
const [rx, ry] = calculateRadius(backgroundImage, x, y, width, height);
if (rx > 0 && rx > 0) {
const radialGradient = this.ctx.createRadialGradient(left + x, top + y, 0, left + x, top + y, rx);
processColorStops(backgroundImage.stops, rx * 2).forEach(colorStop =>
radialGradient.addColorStop(colorStop.stop, asString(colorStop.color))
);
this.path(path);
this.ctx.fillStyle = radialGradient;
if (rx !== ry) {
// transforms for elliptical radial gradient
const midX = container.bounds.left + 0.5 * container.bounds.width;
const midY = container.bounds.top + 0.5 * container.bounds.height;
const f = ry / rx;
const invF = 1 / f;
this.ctx.save();
this.ctx.translate(midX, midY);
this.ctx.transform(1, 0, 0, f, 0, 0);
this.ctx.translate(-midX, -midY);
this.ctx.fillRect(left, invF * (top - midY) + midY, width, height * invF);
this.ctx.restore();
} else {
this.ctx.fill();
}
}
}
index--;
}
}
const calculateBackgroundCurvedPaintingArea = (clip: BACKGROUND_CLIP, curves: BoundCurves): Path[] => {
switch (clip) {
case BACKGROUND_CLIP.BORDER_BOX:
return calculateBorderBoxPath(curves);
case BACKGROUND_CLIP.CONTENT_BOX:
return calculateContentBoxPath(curves);
/`xfy`/
case BACKGROUND_CLIP.TEXT:
return calculateContentBoxPath(curves);
case BACKGROUND_CLIP.PADDING_BOX:
default:
return calculatePaddingBoxPath(curves);
}
};
在所有的BACKGROUND的文件里把缺少的TEXT加上;
在写完这个的时候我编译的时候把ts降级,因为这是三四年前的代码了,
还有一些坑告诉大家:
在我用的时候,我没有把打包之后的dist文件传上去没有引进成功,切记一定要把编译好的dist文件传入上去
最后,大家好,我是好人!!!