緣起
近期在使用 html2canvas
插件生成圖片時账磺,發(fā)現(xiàn)對于 svg
元素支持不是很好临梗。
而且查閱網(wǎng)友的解決方案時宇挫, 發(fā)現(xiàn)是一份原文和N份copy捧书, 總體來說解決方案有以下幾種:
0吹泡、使用 canvg
插件轉(zhuǎn)換 svg 元素
//以下是對svg的處理
var nodesToRecover = [];
var nodesToRemove = [];
var svgElem = $("#divReport").find('svg');//divReport為需要截取成圖片的dom的id
svgElem.each(function (index, node) {
var parentNode = node.parentNode;
var svg = node.outerHTML.trim();
var canvas = document.createElement('canvas');
canvg(canvas, svg);
if (node.style.position) {
canvas.style.position += node.style.position;
canvas.style.left += node.style.left;
canvas.style.top += node.style.top;
}
nodesToRecover.push({
parent: parentNode,
child: node
});
parentNode.removeChild(node);
nodesToRemove.push({
parent: parentNode,
child: canvas
});
parentNode.appendChild(canvas);
});
這里有 nodesToRecover
和 nodesToRemove
兩個變量,猜測應(yīng)該是方便回滾用经瓷, 但是并沒有回滾的相關(guān)代碼爆哑。
1、把 svg 元素轉(zhuǎn)換為圖片舆吮,然后再轉(zhuǎn)換成 canvas元素
//允許跨域獲取揭朝,否則百度地圖不能生成圖片
const opts = {
useCORS: true,
ignoreElements: el => {
const tagName = el.tagName.toLowerCase();
const list = ['head', 'body', 'style', 'title', 'meta']
if(list.includes(tagName)) return false;
// id="extra" 下所有節(jié)點(diǎn)忽略
if(el.id === 'extra') return true;
return false;
},
// TODO:: SVG to canvas
onclone(cloneDom) {
const svgElems = $(cloneDom).find('svg');
svgElems.each(function (index, node) {
let parentNode = node.parentNode;
const svg_string = (node.outerHTML || xmlserializer.serializeToString(node)).trim()
const img = new Image();
img.src = 'data:image/svg+xml;charset=utf-8,' + svg_string;
img.crossOrigin = 'anonymous';
img.onload = function(){
const width = parseFloat($(node).css('width'));
const height = parseFloat($(node).css('height'));
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
parentNode.appendChild(canvas).
parentNode.removeChild(node);
}
});
}
}
我發(fā)現(xiàn)百度地圖上的 svg
元素是有絕對定位和偏移的, 他們的方案不能解決偏移的問題色冀。
性空
后來測試方案0并不成功潭袱。 測試方案一,是因?yàn)闆]有追加圖片到document 里面锋恬,導(dǎo)致沒有觸發(fā)onload方法失敗的敌卓, 這里進(jìn)行如下改進(jìn):
- 0、把當(dāng)前頁面的svg元素轉(zhuǎn)換為 canvas 元素:
// 把svg轉(zhuǎn)換為canvas
async convertSvg2Canvas() {
const svgElms = document.getElementsByTagName('svg');
// 回調(diào)
const callbacks = [];
for(let svg of svgElms) {
const parentElement = svg.parentElement;
const img = new Image();
img.src = `data:image/svg+xml,${encodeURIComponent((new XMLSerializer()).serializeToString(svg))}`;
img.crossOrigin = 'anonymous';
img.onload = async () => {
const width = parseFloat(svg.style.width);
const height = parseFloat(svg.style.height);
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
parentElement.append(canvas);
svg.remove();
img.remove();
};
parentElement.append(img);
callbacks.push(img.onload);
}
//await this.axios.all(callbacks);
await Promise.all(callbacks);
},
- 1伶氢、等待svg轉(zhuǎn)換完成之后趟径,再使用html2canvas 截圖:
async getPreviewImg() {
//顯示加載圖標(biāo)
this.$store.dispatch('showDataloader');
await Promise.all([this.loadScript('/static/js/html2canvas.min.js'), this.convertSvg2Canvas()]);
//允許跨域獲取,否則百度地圖不能生成圖片
const opts = {
useCORS: true
}
const canvasObj = await html2canvas(document.getElementById('poster_context'), opts);
var context = canvasObj.getContext('2d');
//防止圖片模糊的設(shè)置
context.mozImageSmoothingEnabled = false;
context.webkitImageSmoothingEnabled = false;
context.msImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;
// png 格式圖片
let imgType = "image/png";
this.canvasSrc = canvasObj.toDataURL(imgType);
this.previewShowFlag = true;
//隱藏加載圖標(biāo)
this.$store.dispatch('hideDataloader');
},
……
說明: 截屏的時候癣防,必須等待 svg
元素全部轉(zhuǎn)換成為 canvas
元素才可以蜗巧,否則是截取不成功的
loadScript
方法用來異步加載js, 本人是全局mixin的蕾盯, 下附 loadScript 方法:
// 動態(tài)加載 js 及 回調(diào)
async loadScript(src, callback = null) {
await new Promise(resolve => {
// 如果已經(jīng)加載了本js幕屹,直接調(diào)用回調(diào)
if (this.checkScriptLoaded(src)) {
return resolve(callback);
}
let scriptNode = document.createElement("script");
scriptNode.setAttribute("type", "text/javascript");
scriptNode.setAttribute("src", src);
document.body.appendChild(scriptNode);
if (scriptNode.readyState) { //IE 判斷
scriptNode.onreadystatechange = () => {
if (scriptNode.readyState == "complete" || scriptNode.readyState == 'loaded') {
return resolve(callback);
}
}
} else {
scriptNode.onload = () => resolve(callback);
}
})
},
// 檢測是否加載了 js 文件
checkScriptLoaded(src) {
const scriptObjs = Array.from(document.getElementsByTagName('script'));
return scriptObjs.find(ele => ele.src.includes(src));
},
2021-11-19 更新:
H5適配ios系統(tǒng)多行文本時截圖錯行的問題
近期使用html2canvas插件截圖時,發(fā)現(xiàn) iphone手機(jī)上,當(dāng)文本超過一行時望拖,截圖后文本排版錯亂了(第二行會縮進(jìn)一個字渺尘,而且右邊把空白都占滿了)
查看CanvasRenderer渲染文本的時候,發(fā)現(xiàn)有個 letter-spacing 屬性:
CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing) {
var _this = this;
if (letterSpacing === 0) {
this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
}
else {
var letters = toCodePoints(text.text).map(function (i) { return fromCodePoint(i); });
letters.reduce(function (left, letter) {
_this.ctx.fillText(letter, left, text.bounds.top + text.bounds.height);
return left + _this.ctx.measureText(letter).width;
}, text.bounds.left);
}
};
如上说敏,如果沒有設(shè)置 letter-spacing的樣式鸥跟,則會使用 canvas 的 fillText方法把文本渲染到畫布上。(但是fillText 對換行文字排版等支持不夠友好)盔沫, 設(shè)置了 letter-spacing屬性医咨,會使用measureText 方法把文字渲染到畫布,雖然不會溢出box架诞,但是第二行還是會有縮進(jìn)的問題……
解決方案就是: 盡量避免文本超過兩行拟淮,當(dāng)文本超過兩行的時候,每行頭尾用 行內(nèi)元素 包一下谴忧, 這樣就會當(dāng)做 html元素去渲染到畫布很泊,極限的可以把每個字符都用行內(nèi)元素包一下……
(to be continued …… )