自己动手实现一个html2canvas
前言
昨天寫了新手引導(dǎo)動畫的4種實(shí)現(xiàn)方式, 里面用到了 html2canvas 于是就順便了解了一下實(shí)現(xiàn)思路.
大概就是 利用 svg 的 foreignObject 標(biāo)簽, 嵌入 dom, 最后再利用 canvas 繪制 svg. 從而實(shí)現(xiàn)最終目的.
先讓大家看看效果
MDN示例
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; 復(fù)制代碼MDN示例其實(shí)寫的很清楚,不過也相對比較簡單一點(diǎn), dom 是已經(jīng)構(gòu)建好的字符串, 其實(shí)我覺得整個(gè)過程里面最麻煩的就是構(gòu)建 dom. 所以接下來,我們就來看看具體怎么實(shí)現(xiàn)吧
第一步 遍歷目標(biāo)節(jié)點(diǎn)的所有子元素,并構(gòu)建對應(yīng)的字符串
/*** 遞歸遍歷所有子節(jié)點(diǎn)* @param element Document Element 要計(jì)算的元素* @param isTop Boolean 是否是最外層元素 **/ function renderDom (element, isTop) {let tag = element.tagName.toLowerCase()let str = `<${tag} `// 最外層的節(jié)點(diǎn),需要加 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 } 復(fù)制代碼這里只做了一個(gè)最簡單的處理,由于是簡單實(shí)現(xiàn),很多特殊情況沒考慮進(jìn)去(如:單標(biāo)簽, img等),有興趣的童鞋可以自己嘗試實(shí)現(xiàn)看看.
最外層的元素, 需要加命名空間,否則無法識別
這里用到的 getElementStyles 就是獲取元素的最終渲染樣式,下一步會實(shí)現(xiàn).
第二步, 獲取元素的最終渲染樣式,并拼接成行內(nèi)樣式
正常的 dom 元素, 是無法直接放在 foreignObject 里面準(zhǔn)確地渲染的, 因?yàn)檫€要涉及到父子元素直接的屬性繼承, 元素默認(rèn)屬性, 非行內(nèi)樣式無法渲染等問題. 所以我們要獲取每個(gè)元素的最終渲染樣式, 然后拼接成行內(nèi)樣式.
如何獲取元素的最終渲染樣式呢? 剛好,瀏覽器有提供一個(gè) window.getComputedStyle() 方法可以做到.
// 計(jì)算每個(gè) dom 的樣式 // 這里本來應(yīng)該直接用 Object.keys + forEach 遍歷取出的 // 但是不知道為什么,遍歷取出的,會渲染不出來,應(yīng)該是某些屬性有問題 // 暫時(shí)沒空去排查那些有問題,所以目前先把常用的直接寫死. function getElementStyles (el) {let css = window.getComputedStyle(el)let style = ''// 尺寸相關(guān)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};`// 顏色相關(guān)style += `color: ${css.color};`style += `background: ${css.background};`// 邊框相關(guān)style += `border: ${css.border};`style += `box-sizing: ${css.boxSizing};`// 位置相關(guān)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};`// 布局相關(guān)style += `display: ${css.display};`style += `flex: ${css.flex};`return style } 復(fù)制代碼第三步, 渲染 svg
把拼接好的 svg 字符串用 Blob 對象 new 出來(Blob真的是個(gè)很強(qiáng)大的對象啊), 然后用 DOMURL.createObjectURL() 轉(zhuǎn)換為 url, 有了url, 接下來就看大家自由發(fā)揮了. 可以直接下載,也可以在 canvas 里繪制. 或者當(dāng)作圖片直接插入到文檔...
// 主入口函數(shù) 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) }// 計(jì)算 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>` }復(fù)制代碼這里順便給個(gè)繪制到 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); } 復(fù)制代碼最后
參考文檔:
MDN: 將 DOM 對象繪制到 canvas 中
MDN: foreignObject
完整的代碼在這里,可以直接運(yùn)行看效果.
本文地址在->個(gè)人技術(shù)帖合集, 歡迎給個(gè) start 或 follow
總結(jié)
以上是生活随笔為你收集整理的自己动手实现一个html2canvas的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: laravel安装laravel-ide
- 下一篇: csharp read excel fi