html2canvas的background-clip:text修复

这些天在做一个海报图自动生成的功能,在这里用到了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文件传入上去

最后,大家好,我是好人!!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值