昨天寫了新手引導動畫的4種實現方式, 裏面用到了 html2canvas
因而就順便了解了一下實現思路.css
大概就是 利用 svg
的 foreignObject
標籤, 嵌入 dom, 最後再利用 canvas 繪製 svg. 從而實現最終目的.html
先讓你們看看效果git
MDN示例github
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
'<foreignObject width="100%" height="100%">' +
'<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
'<em>I</em> like' +
'<span style="color:white; text-shadow:0 0 2px blue;">' +
'cheese</span>' +
'</div>' +
'</foreignObject>' +
'</svg>';
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
var url = DOMURL.createObjectURL(svg);
img.onload = function () {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
img.src = url;
複製代碼
MDN示例其實寫的很清楚,不過也相對比較簡單一點, dom 是已經構建好的字符串, 其實我以爲整個過程裏面最麻煩的就是構建 dom. 因此接下來,咱們就來看看具體怎麼實現吧web
/** * 遞歸遍歷全部子節點 * @param element Document Element 要計算的元素 * @param isTop Boolean 是不是最外層元素 **/
function renderDom (element, isTop) {
let tag = element.tagName.toLowerCase()
let str = `<${tag} `
// 最外層的節點,須要加 xmlns 命名空間
isTop && (str += `xmlns="http://www.w3.org/1999/xhtml" `)
str += ` style="${getElementStyles(element)}">\n`
if (element.children.length) {
// 遞歸子元素
for (let el of element.children) {
str += renderDom(el)
}
} else {
str += element.innerHTML
}
str += `</${tag}>\n`
return str
}
複製代碼
這裏只作了一個最簡單的處理,因爲是簡單實現,不少特殊狀況沒考慮進去(如:單標籤, img等),有興趣的童鞋能夠本身嘗試實現看看.canvas
最外層的元素, 須要加命名空間,不然沒法識別瀏覽器
這裏用到的 getElementStyles
就是獲取元素的最終渲染樣式,下一步會實現.app
正常的 dom 元素, 是沒法直接放在 foreignObject
裏面準確地渲染的, 由於還要涉及到父子元素直接的屬性繼承, 元素默認屬性, 非行內樣式沒法渲染等問題. 因此咱們要獲取每一個元素的最終渲染樣式, 而後拼接成行內樣式.dom
如何獲取元素的最終渲染樣式呢? 恰好,瀏覽器有提供一個 window.getComputedStyle()
方法能夠作到.svg
// 計算每一個 dom 的樣式
// 這裏原本應該直接用 Object.keys + forEach 遍歷取出的
// 可是不知道爲何,遍歷取出的,會渲染不出來,應該是某些屬性有問題
// 暫時沒空去排查那些有問題,因此目前先把經常使用的直接寫死.
function getElementStyles (el) {
let css = window.getComputedStyle(el)
let style = ''
// 尺寸相關
style += `width:${css.width};`
style += `height: ${css.height};`
style += `line-height: ${css.lineHeight};`
style += `max-height: ${css.maxHeight};`
style += `min-height: ${css.minHeight};`
style += `max-width: ${css.maxWidth};`
style += `min-width: ${css.minWidth};`
style += `font-size: ${css.fontSize};`
// 顏色相關
style += `color: ${css.color};`
style += `background: ${css.background};`
// 邊框相關
style += `border: ${css.border};`
style += `box-sizing: ${css.boxSizing};`
// 位置相關
style += `margin: ${css.margin};`
style += `padding: ${css.padding};`
style += `position: ${css.position};`
style += `left: ${css.left};`
style += `right: ${css.right};`
style += `top: ${css.top};`
style += `bottom: ${css.bottom};`
// 佈局相關
style += `display: ${css.display};`
style += `flex: ${css.flex};`
return style
}
複製代碼
把拼接好的 svg 字符串用 Blob 對象
new 出來(Blob真的是個很強大的對象啊), 而後用 DOMURL.createObjectURL()
轉換爲 url, 有了url, 接下來就看你們自由發揮了. 能夠直接下載,也能夠在 canvas 裏繪製. 或者看成圖片直接插入到文檔...
// 主入口函數
function shotScreen () {
let target = document.querySelector('.content')
let data = getSvgDomString(target)
let DOMURL = window.URL || window.webkitURL || window;
let img = new Image();
let svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
let url = DOMURL.createObjectURL(svg);
img.src = url;
document.body.appendChild(img)
}
// 計算 svg 的字符串
function getSvgDomString (element) {
return ` <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">\n <foreignObject width="100%" height="100%">\n ${renderDom(element, 1)} </foreignObject>\n </svg>`
}
複製代碼
這裏順便給個繪製到 canvas 裏的代碼
// 若是想畫到 canvas 裏面
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let img = new Image();
img.onload = function () {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
複製代碼
參考文檔:
完整的代碼在這裏,能夠直接運行看效果.
本文地址在->我的技術帖合集, 歡迎給個 start 或 follow