使用洋葱圈模型重构html-export
先上个 GitHub 链接
前段时间在工作中遇到了一个将页面导出为图片的需求,考虑到这个需求比较常见,于是将其抽象出来放在了 GitHub 上,原版的代码是这样实现的
1 | export default class Exporter { |
整个代码的流程并不复杂,主要包括以下几步
获取导出元素原始高度,取消其滚动,让不导出元素消失
将整个页面通过 html2canvas 转换成图片
下载图片
让页面复原
但是,原版的实现将上述几个流程混杂在一起,整个代码很长,可读性和可维护性都不强,且很难扩展,因此考虑对他进行重构
再次分析业务流程,可以更细致化的流程
1 | 1. 导出元素高度设置为auto |
可以看到,1、2、3都是在对同一类对象进行处理,但是被中间的核心部分将两个阶段隔开了,这和 Koa 的洋葱圈模型十分相似
看一个 Koa 洋葱圈的例子
1 | app.use(async (ctx, next) => { |
执行这个代码片段可以发现,输出为 1342,和 html-export 实际的执行顺序是一致的,可以通过对 Koa 洋葱圈的模拟来达到想要的效果
html-export 中的洋葱圈是这样的
1 | export default class Onion { |
中间件以对象的形式注册,对象中的 before 方法和 after 方法可以按由外到内在由内到外的方式调用
基于这种形式,分别编写四个中间件
用于处理导出元素高度
middleware/handleHeight.js 1
2
3
4
5
6
7
8
9
10
11
12export default function handleHeight(exportEle) {
const ele = document.querySelector(exportEle)
const eleHeight = window.getComputedStyle(ele).getPropertyValue('height')
return {
before: () => {
ele.style.height = 'auto'
},
after: () => {
ele.style.height = eleHeight
}
}
}用于处理滚动元素
middleware/handleScroll.js 1
2
3
4
5
6
7
8
9
10
11
12export default function handleScroll(scrollEle) {
const ele = document.querySelector(scrollEle)
const eleOverflow = window.getComputedStyle(ele).getPropertyValue('overflow')
return {
before: () => {
ele.style.overflow = 'visible'
},
after: () => {
ele.style.overflow = eleOverflow
}
}
}用于处理不导出元素
middleware/handleIgnore.js 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23export default function handleIgnore(ignoreEle) {
let displayMap = new Map()
ignoreEle.forEach(ele => {
const dom = document.querySelector(ele)
const display = window.getComputedStyle(dom).getPropertyValue('display')
displayMap.set(ele, display)
})
return {
before: () => {
ignoreEle.forEach(ele => {
const dom = document.querySelector(ele)
dom.style.display = 'none'
})
},
after: () => {
ignoreEle.forEach(ele => {
const dom = document.querySelector(ele)
dom.style.display = displayMap.get(ele)
})
displayMap = null
}
}
}核心导出模块
middleware/handleExport.js 1
2
3
4
5
6
7
8
9
10
11
12
13export default function handleExport(name, exportEle, options = {}) {
return {
before: () => {
html2canvas(document.querySelector(exportEle), options).then(canvas => {
return canvas.toDataURL('image/png', 1)
}).then(url => {
download(name, url)
})
},
after: () => {}
}
}
最终主流程可以简化为
1 | export default function htmlExport( |
可以看到,改造后的代码逻辑清晰,易于维护,同时如果有需要添加的功能可以很方便的通过中间件自行添加