前言

昨天写了新手引导动画的4种实现方式,
里面用到了 html2canvas 于是就顺便了解了一下实现思路.

大概就是 利用 svgforeignObject 标签, 嵌入 dom, 最后再利用 canvas 绘制 svg. 从而实现最终目的.

先让大家看看效果

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;

MDN示例其实写的很清楚,不过也相对比较简单一点, dom 是已经构建好的字符串, 其实我觉得整个过程里面最麻烦的就是构建 dom. 所以接下来,我们就来看看具体怎么实现吧

第一步 遍历目标节点的所有子元素,并构建对应的字符串


/**
* 递归遍历所有子节点
* @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等),有兴趣的童鞋可以自己尝试实现看看.

最外层的元素, 需要加命名空间,否则无法识别

这里用到的 getElementStyles 就是获取元素的最终渲染样式,下一步会实现.

第二步, 获取元素的最终渲染样式,并拼接成行内样式

正常的 dom 元素, 是无法直接放在 foreignObject 里面准确地渲染的, 因为还要涉及到父子元素直接的属性继承, 元素默认属性, 非行内样式无法渲染等问题.
所以我们要获取每个元素的最终渲染样式, 然后拼接成行内样式.

如何获取元素的最终渲染样式呢? 刚好,浏览器有提供一个 window.getComputedStyle() 方法可以做到.


// 计算每个 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

把拼接好的 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);
}

最后

参考文档:

MDN: 将 DOM 对象绘制到 canvas 中

MDN: foreignObject

完整的代码在这里,可以直接运行看效果.

本文地址在->个人技术帖合集, 欢迎给个 start 或 follow

自己动手实现一个html2canvas的更多相关文章

  1. 《动手实现一个网页加载进度loading》

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈" ...

  2. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  3. 自己动手实现一个简单的JSON解析器

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...

  4. 动手实现一个vue中的模态对话框组件

    写在前面 对话框是很常用的组件 , 在很多地方都会用到,一般我们可以使用自带的alert来弹出对话框,但是假如是设计 出的图该怎么办呢 ,所以我们需要自己写一个对话框,并且如果有很多地方都用到,那我们 ...

  5. 超详细动手搭建一个Vuepress站点及开启PWA与自动部署

    超详细动手搭建一个Vuepress站点及开启PWA与自动部署 五一之前就想写一篇关于Vuepress的文章,结果朋友结婚就不了了之了. 记得最后一定要看注意事项! Vuepress介绍 官网:http ...

  6. 自己动手实现一个WEB服务器

    自己动手实现一个 Web Server 项目背景 最近在重温WEB服务器的相关机制和原理,为了方便记忆和理解,就尝试自己用Java写一个简化的WEB SERVER的实现,功能简单,简化了常规服务器的大 ...

  7. 动手写一个简单版的谷歌TPU-矩阵乘法和卷积

    谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...

  8. 死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...

  9. 自己动手编写一个VS插件(五)

    作者:朱金灿 来源:http://blog.csdn.net/clever101 继续编写VisualStudio插件.这次我编写的插件叫DevAssist(意思是开发助手).在看了前面的文章之后你知 ...

随机推荐

  1. JavaWeb_初识监听器Listener

    监听器(listener):对项目起到监听的作用,它能感知到包括request(请求域),session(会话域)和applicaiton(应用程序)的初始化和属性的变化 监听器是Servlet规范中 ...

  2. Applink使用原理解析

    简介 通过 Link这个单词我们可以看出这个是一种链接,使用此链接可以直接跳转到 APP,常用于应用拉活,跨应用启动,推送通知启动等场景. 流程 在AS 上其实已经有详细的使用步骤解析了,这里给大家普 ...

  3. 揭开HTTPS的神秘面纱

    摘自:https://www.cnblogs.com/hujingnb/p/11789728.html 揭开HTTPS的神秘面纱   在说HTTP前,一定要先介绍一下HTTP,这家伙应该不用过多说明了 ...

  4. Java线程细节

    启动一个线程是用  run() 还是 start()?启动一个线程是调用 start()方法,启动线程并调用 run 方法 线程的基本概念.线程的基本状态以及状态之间的关系线程是进程内的并发,没有自已 ...

  5. docker-compose常用命令(持续更新...)

    build 构建或重建服务 help 命令帮助 kill 杀掉容器 logs 显示容器的输出内容 port 打印绑定的开放端口 ps 显示容器 pull 拉取服务镜像 restart 重启服务 rm ...

  6. spark streaming 2: DStream

    DStream是类似于RDD概念,是对数据的抽象封装.它是一序列的RDD,事实上,它大部分的操作都是对RDD支持的操作的封装,不同的是,每次DStream都要遍历它内部所有的RDD执行这些操作.它可以 ...

  7. 清明 DAY 4

    ~~~~~zhx并没有讲完~~~~~~QAQ~~~~~~ (其实并没有更完)

  8. Docker-----版本选择

    版本演变 17.03 版本以前 Docker CE 在 17.03 版本之前叫 Docker Engine, Docker Engine 的版本号范围: 0.1.0 ~ 1.13.1 17.03 版本 ...

  9. AS之Lint 工具

    AndroidStudio内置的Lint工具,对app中的代码规范带来了极大的方便.对内存泄漏.代码冗余.代码安全.国际化.代码规范等很多方面都能检测,是一款非常强大的工具! 使用: 在AS--> ...

  10. Kotlin之环境的配置和搭建

    第一步 . 安装和配置JDK http://www.cnblogs.com/loaderman/p/6402948.html 第二步 安装和下载Kotlin 进入Kotlin官网 :https :// ...