当前位置: 首页 > 工具软件 > html2canvas > 使用案例 >

修改html2canvas源码,解决图片跨域问题

羊渝
2023-12-01

url图片显示并不会跨域,
只有要通过xhr获取其blob对象才会提示跨域
这就是为何通过

html2canvas(dom,{
	allowTaint: true //允许污染画布
}).then(canvas=>{
	document.body.appendChild(canvas);
})

以上的canvas是可以显示出来的,但仅仅是可以显示,如果dom中存在跨域url的图片,那么生成的canvas是被污染的,无法执行canvas.toDataURL();

html2canvas(dom,{
	allowTaint: true //允许污染画布
}).then(canvas=>{
	document.body.appendChild(canvas);
	canvas.toDataURL(); //提示报错无法导出收到污染的画布
})

此时,如果图片所在的服务器设置了cors请求头,即Access-Control-Allow-Origin=*或者你页面所在的域名
那么加上useCORS: true 允许跨域即可,源码中会把img加上crossOrigin="anonymous"属性从而去申请跨域请求头(最低支持到IE10)

html2canvas(dom,{
	allowTaint: true, //允许污染画布
	useCORS: true
}).then(canvas=>{
	document.body.appendChild(canvas);
	canvas.toDataURL(); //提示报错无法导出收到污染的画布
})

最后,如果图片所在服务器没有提供cors请求头,那么只能通过搭建服务器转发请求的方式,常用的有nginx配置代理,当然java,.net也都有实现
例如源图片地址来自http://ip1:port1/img/test.png
nginx代理后的地址为http://ip2:port2/img/test.png,同时代理配置了Access-Control-Allow-Origin=*
此时html2canvas需要稍作修改才能实现以上的代理请求访问

html2canvas(dom,{
	allowTaint: true, //允许污染画布
	//useCORS: true, //使用代理需要设置为false或者不设置
	proxy:"http://ip2:port2" //nginx转发后的ip和端口
}).then(canvas=>{
	document.body.appendChild(canvas);
	canvas.toDataURL(); //提示报错无法导出收到污染的画布
})

以上实际会请求这么个地址
http://ip2:port2?url=http://ip1:port1/img/test.png&responseType=blob
显然,我们的nginx代理配置的不是这样的,
此时,你可以配置nginx用正则的方式去匹配以上的格式的地址,
这儿给出了修改html2canvas的实现
这儿是html2canvas源码中proxy的实现

Cache.prototype.proxy = function (src) {
            var _this = this;
            var proxy = this._options.proxy;
            if (!proxy) {
                throw new Error('No proxy defined');
            }
            var key = src.substring(0, 256);
            return new Promise(function (resolve, reject) {
                var responseType = FEATURES.SUPPORT_RESPONSE_TYPE ? 'blob' : 'text';
                var xhr = new XMLHttpRequest();
                xhr.onload = function () {
                    if (xhr.status === 200) {
                        if (responseType === 'text') {
                            resolve(xhr.response);
                        }
                        else {
                            var reader_1 = new FileReader();
                            reader_1.addEventListener('load', function () { return resolve(reader_1.result); }, false);
                            reader_1.addEventListener('error', function (e) { return reject(e); }, false);
                            reader_1.readAsDataURL(xhr.response);
                        }
                    }
                    else {
                        reject("Failed to proxy resource " + key + " with status code " + xhr.status);
                    }
                };
                xhr.onerror = reject;
                xhr.open('GET', proxy + "?url=" + encodeURIComponent(src) + "&responseType=" + responseType);
                if (responseType !== 'text' && xhr instanceof XMLHttpRequest) {
                    xhr.responseType = responseType;
                }
                if (_this._options.imageTimeout) {
                    var timeout_1 = _this._options.imageTimeout;
                    xhr.timeout = timeout_1;
                    xhr.ontimeout = function () { return reject("Timed out (" + timeout_1 + "ms) proxying " + key); };
                }
                xhr.send();
            });
        };

我们可以同样的方式在这个方法下新增一个ipProxy表示只替换ip的方式代理

Cache.prototype.ipProxy = function (src) {
            var _this = this;
            var proxy = this._options.ipProxy;
            if (!proxy) {
                throw new Error('No proxy defined');
            }
			if(proxy.substr(-1,1)!="/"){
				proxy=proxy+"/";
			}
            var key = src.substring(0, 256);
            return new Promise(function (resolve, reject) {
                var responseType = FEATURES.SUPPORT_RESPONSE_TYPE ? 'blob' : 'text';
                var xhr = new XMLHttpRequest();
                xhr.onload = function () {
                    if (xhr.status === 200) {
                        if (responseType === 'text') {
                            resolve(xhr.response);
                        }
                        else {
                            var reader_1 = new FileReader();
                            reader_1.addEventListener('load', function () { return resolve(reader_1.result); }, false);
                            reader_1.addEventListener('error', function (e) { return reject(e); }, false);
                            reader_1.readAsDataURL(xhr.response);
                        }
                    }
                    else {
                        reject("Failed to proxy resource " + key + " with status code " + xhr.status);
                    }
                };
                xhr.onerror = reject;
				var _src = src.substr(src.indexOf(':')+(src.substr(0,4)==='http'?3:0));
				xhr.open("GET",proxy+_src.substr(_src.indexOf('/')+1));
                //xhr.open('GET', proxy + "?url=" + encodeURIComponent(src) + "&responseType=" + responseType);
                if (responseType !== 'text' && xhr instanceof XMLHttpRequest) {
                    xhr.responseType = responseType;
                }
                if (_this._options.imageTimeout) {
                    var timeout_1 = _this._options.imageTimeout;
                    xhr.timeout = timeout_1;
                    xhr.ontimeout = function () { return reject("Timed out (" + timeout_1 + "ms) proxying " + key); };
                }
                xhr.send();
            });
        };

同时,新增一个option.ipProxy的解析

Cache.prototype.loadImage = function (key) {
            return __awaiter(this, void 0, void 0, function () {
                var isSameOrigin, useCORS, useProxy, src,useIPProxy; //新增变量useIPProxy
                var _this = this;
                return __generator(this, function (_a) {
                    switch (_a.label) {
                        case 0:
                            isSameOrigin = CacheStorage.isSameOrigin(key);
                            useCORS = !isInlineImage(key) && this._options.useCORS === true && FEATURES.SUPPORT_CORS_IMAGES && !isSameOrigin;
                            useProxy = !isInlineImage(key) &&
                                !isSameOrigin &&
                                typeof this._options.proxy === 'string' &&
                                FEATURES.SUPPORT_CORS_XHR &&
                                !useCORS;
                                //对useIPProxy 赋值
							useIPProxy = !isInlineImage(key) &&
							    !isSameOrigin &&
                                typeof this._options.ipProxy === 'string' &&
                                FEATURES.SUPPORT_CORS_XHR &&
                                !useCORS;
                            if (!isSameOrigin && this._options.allowTaint === false && !isInlineImage(key) && !useProxy && !useCORS) {
                                return [2 /*return*/];
                            }
                            src = key;
                            //如果是需要ipproxy的提前返回并调用ipProxy方法
							if (useIPProxy) return [4 /*yield*/, this.ipProxy(src)];
                            if (!useProxy) return [3 /*break*/, 2];
                            return [4 /*yield*/, this.proxy(src)];
                        case 1:
                            src = _a.sent();
                            _a.label = 2;
                        case 2:
                            Logger.getInstance(this.id).debug("Added image " + key.substring(0, 256));
                            return [4 /*yield*/, new Promise(function (resolve, reject) {
                                    var img = new Image();
                                    img.crossOrigin = '';
                                    img.onload = function () { return resolve(img); };
                                    img.onerror = reject;
                                    //ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous
                                    if (isInlineBase64Image(src) || useCORS) {
                                        img.crossOrigin = 'anonymous';
                                    }
                                    img.src = src;
                                    if (img.complete === true) {
                                        // Inline XML images may fail to parse, throwing an Error later on
                                        setTimeout(function () { return resolve(img); }, 500);
                                    }
                                    if (_this._options.imageTimeout > 0) {
                                        setTimeout(function () { return reject("Timed out (" + _this._options.imageTimeout + "ms) loading image"); }, _this._options.imageTimeout);
                                    }
                                })];
                        case 3: return [2 /*return*/, _a.sent()];
                    }
                });
            });
        };

修改后的html2canvas就可以实现ip代理而不是拼接url代理了
此时你可以发现访问的不再是
http://ip2:port2?url=http://ip1:port1/img/test.png&responseType=blob
而是
http://ip2:port2/img/test.png

 类似资料: