前言
最近的需求是将一个低码报表(里面分布了很多图形,比如表格,饼图等等)防截断导出,翻了很多的文章,要么实现复杂,要么就是有漏洞?什么漏洞,往下看 其实一个页面导出,是不是如果碰上一个图形,在分页的时候被截断,那就得把这个图形相关的元素(比如在它旁边的图形以及它自己本身)往下挪,放去下一页?
思考
- 先考虑我们用什么当作截断的工具呢?是否可以通过getImageData()获取页面像素数据,将像素跟raga(255,255,255,1)做对比,如果存在10px高度跟宽度为100%页面宽度,且颜色为raga(255,255,255,1)的间隔条就视为一个分割线,就是一个分页的标志?
- 我们怎么把要截断的元素往下挪去另一页再打印呢? 其实我们可以这么做:将整个页面全部转成canvas以后,先截取一个A4纸高度页面信息,从下往上判断是否存在一个分界线,如果存在,那就将分界线以下的页面导出成一张pdf。整个操作递归,直到打出到最后一页
实现
/**
*
* @param {Object} ref 导出页面的节点
* @param {*} pageName 导出pdf名称
* @param {*} margin 截断分界线高度
* @param {*} colors 对比颜色数组
*/
// 设计思路:position:位置指针,leftHeight:整个canvas剩余高度,每次获取一页a4纸对应的canvas的高度 a4HeightCanvas,
// 从下往上开始检测,如果碰到margin*2的匹配色高度,则认为这是一条可分割线,
// 于是可以生成一张高度为分割线以上部分height的pdf,此时的position = position + height ,leftHeight = leftHeight-height,
// 如果canvas剩下的高度 leftHeight 大于0,则继续上面的步骤
// 注意:如果两条分割线中间的部分超过a4HeightCanvas,生成的height 将为a4HeightCanvas ,图像会被截断!
// 此时,只能由报表用户调整图表位置重新导出pdf
function toChangeRealPdf (ref, pageName, margin = 10) {
const color1 = 255
const color2 = 255
const color3 = 255
// grid-stack
html2canvas(ref,
scale: window.devicePixelRatio * 2 // 增加清晰度
}).then(canvas => {
// 未生成pdf的canvas页面高度
let leftHeight = canvas.height
const a4Width = 190
const a4Height = 277 // A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
// 一页pdf显示html页面生成的canvas高度;
const a4HeightCanvas = Math.floor(canvas.width / a4Width * a4Height)
const context = canvas.getContext('2d')
const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
// pdf页面偏移
let position = 0
const pdf = new jsPDF('p', 'mm', 'a4') // A4纸,纵向
let index = 1
const canvas1 = document.createElement('canvas')
let height
pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen')
const createImgPdf = (canvas) => {
if (leftHeight > 0) {
index++
let checkCount = 0
if (leftHeight > a4HeightCanvas) {
let i = position + a4HeightCanvas
// 获取图像数据
// const context = canvas.getContext('2d')
const canvasW = canvas.width
for (i = position + a4HeightCanvas; i >= position; i--) {
let isWrite = true
for (let j = 0; j < canvasW; j++) {
// 每四个数组元素代表了一个像素点的RGBA信息,每个元素数值介于0~255,进行颜色数据匹配,找出分界线
const c = imageData.data
const index = i * canvasW + j
const lastIndex = i * canvasW - j - 1
const index4 = 4 * index
if (!(c[index4] == color1 && c[index4 + 1] == color2 && c[index4 + 2] == color3 && c[index4 + 3] == 255)) {
isWrite = false
break
}
}
if (isWrite) {
checkCount++
if (checkCount >= margin * 2) {
break
}
} else {
checkCount = 0
}
}
height = Math.round(i - position) || Math.min(leftHeight, a4HeightCanvas)
if (height <= 0) {
height = a4HeightCanvas
}
} else {
height = leftHeight
}
canvas1.width = canvas.width
canvas1.height = height
const ctx = canvas1.getContext('2d')
ctx.drawImage(canvas, 0, position, canvas.width, height, 0, 0, canvas.width, height)
if (position != 0) {
pdf.addPage()
}
pdf.addImage(canvas1, 'JPEG', 10, 10, a4Width, a4Width / canvas1.width * height)
leftHeight -= height
position += height
}
}
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < a4HeightCanvas) {
pdf.addImage(canvas, 'JPEG', 10, 10, a4Width, a4Width / canvas.width * leftHeight)
} else {
while (leftHeight > 0) {
createImgPdf(canvas)
}
}
pdf.save(pageName + '.pdf')
})
}
漏洞
思考:这会不会出现问题?比如,页面背景色刚好就是rgba(255,255,255,1),这样的话,如果一个图表特别长,刚好图表的内部有很多空白部分,高度超过10,这时候,会不会就会导致这个图表被截断了?
处于截断页面下部的图表表现:
导出以后,图表被截断:
这种情况应该如何优化呢?
我们可以将页面转成canvas,然后将页面拷贝一份,并设置一个对比色,这个颜色可以自定义,然后将这两份数据进行对比,再打印
function toChangeRealPdf (result, ref, pageName, loading, margin = 10, colors = [0, 153, 217]) {
const [color1, color2, color3] = colors
// grid-stack
if (result) {
html2canvas(ref, {
onclone: (document) => {
document.getElementsByClassName('grid-stack')[0].style.backgroundColor = `rgba(${color1},${color2},${color3},1)`
},
scale: window.devicePixelRatio * 2 // 增加清晰度
}).then(canvas => {
// 未生成pdf的canvas页面高度
let leftHeight = canvas.height
const a4Width = 190
const a4Height = 277 // A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
// 一页pdf显示html页面生成的canvas高度;
const a4HeightCanvas = Math.floor(canvas.width / a4Width * a4Height)
const context = canvas.getContext('2d')
const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
// pdf页面偏移
let position = 0
const pdf = new jsPDF('p', 'mm', 'a4') // A4纸,纵向
let index = 1
const canvas1 = document.createElement('canvas')
let height
pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen')
const createImgPdf = (canvas) => {
if (leftHeight > 0) {
index++
let checkCount = 0
if (leftHeight > a4HeightCanvas) {
let i = position + a4HeightCanvas
// 获取图像数据
// const context = canvas.getContext('2d')
const canvasW = canvas.width
for (i = position + a4HeightCanvas; i >= position; i--) {
let isWrite = true
for (let j = 0; j < canvasW; j++) {
// 每四个数组元素代表了一个像素点的RGBA信息,每个元素数值介于0~255,进行颜色数据匹配,找出分界线
const c = imageData.data
const index = i * canvasW + j
const lastIndex = i * canvasW - j - 1
const index4 = 4 * index
if (!(c[index4] == color1 && c[index4 + 1] == color2 && c[index4 + 2] == color3 && c[index4 + 3] == 255)) {
isWrite = false
break
}
}
if (isWrite) {
checkCount++
if (checkCount >= margin * 2) {
break
}
} else {
checkCount = 0
}
}
height = Math.round(i - position) || Math.min(leftHeight, a4HeightCanvas)
if (height <= 0) {
height = a4HeightCanvas
}
} else {
height = leftHeight
}
canvas1.width = canvas.width
canvas1.height = height
console.log(index, 'height:', height, 'pos', position)
const ctx = canvas1.getContext('2d')
ctx.drawImage(result, 0, position, canvas.width, height, 0, 0, canvas.width, height)
if (position != 0) {
pdf.addPage()
}
pdf.addImage(canvas1, 'JPEG', 10, 10, a4Width, a4Width / canvas1.width * height)
leftHeight -= height
position += height
}
}
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < a4HeightCanvas) {
// const pageData = result.toDataURL('image/jpeg', 1.0)
pdf.addImage(result, 'JPEG', 10, 10, a4Width, a4Width / result.width * leftHeight)
} else {
while (leftHeight > 0) {
createImgPdf(canvas)
}
}
pdf.save(pageName + '.pdf')
// console.log(that.$createDate())
loading.close()
})
}
}
export function downloadPdf (ref, pageName, margin, colors) {
const loading = Loading.service({
fullscreen: true,
lock: true,
text: 'Loading',
spinner: 'icon-loading',
background: 'rgba(255, 255, 255, 0.7)'
})
// console.log(that.$createDate())
html2canvas(ref, {
scale: window.devicePixelRatio * 2 // 增加清晰度
}).then(canvas => {
toChangeRealPdf(canvas, ref, pageName, loading, margin, colors)
})
}
效果图:
嘿嘿,是不是就好了??
现在又发现一个问题,toChangeRealPdf方法中,找出分界线的代码这是双层遍历,整个页面的imageData.data是一个非常庞大的数据,目前我的测试页面组件很少,可能没多大影响,但是如果一张报表组件很多,数据很多的时候,就会导致页面导出很慢
for (i = position + a4HeightCanvas; i >= position; i--) {
let isWrite = true
for (let j = 0; j < canvasW; j++) {
// 每四个数组元素代表了一个像素点的RGBA信息,每个元素数值介于0~255,进行颜色数据匹配,找出分界线
const c = imageData.data
const index = i * canvasW + j
const lastIndex = i * canvasW - j - 1
const index4 = 4 * index
if (!(c[index4] == color1 && c[index4 + 1] == color2 && c[index4 + 2] == color3 && c[index4 + 3] == 255)) {
isWrite = false
break
}
}
算法又如何优化
如何优化呢? 我们可以将页面分成两边,比对的时候,从左右双头进行
优化前,toChangeRealPdf函数性能分析
优化后,toChangeRealPdf执行时间明显减少
function toChangeRealPdf (result, ref, pageName, loading, margin = 10, colors = [0, 153, 217]) {
const [color1, color2, color3] = colors
// grid-stack
if (result) {
html2canvas(ref, {
onclone: (document) => {
document.getElementsByClassName('grid-stack')[0].style.backgroundColor = `rgba(${color1},${color2},${color3},1)`
},
scale: window.devicePixelRatio * 2 // 增加清晰度
}).then(canvas => {
// 未生成pdf的canvas页面高度
let leftHeight = canvas.height
const a4Width = 190
const a4Height = 277 // A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
// 一页pdf显示html页面生成的canvas高度;
const a4HeightCanvas = Math.floor(canvas.width / a4Width * a4Height)
const context = canvas.getContext('2d')
const imageData = context.getImageData(0, 0, canvas.width, canvas.height)
// pdf页面偏移
let position = 0
const pdf = new jsPDF('p', 'mm', 'a4') // A4纸,纵向
let index = 1
const canvas1 = document.createElement('canvas')
let height
pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen')
const createImgPdf = (canvas) => {
if (leftHeight > 0) {
index++
let checkCount = 0
if (leftHeight > a4HeightCanvas) {
let i = position + a4HeightCanvas
// 获取图像数据
// const context = canvas.getContext('2d')
const canvasW = canvas.width
const mid = parseInt(canvasW / 2)
for (i = position + a4HeightCanvas; i >= position; i--) {
let isWrite = true
for (let j = 0; j < mid; j++) {
// 每四个数组元素代表了一个像素点的RGBA信息,每个元素数值介于0~255
const c = imageData.data
const index = i * canvasW + j
const lastIndex = i * canvasW - j - 1
const index4 = 4 * index
const lastIndex4 = 4 * lastIndex
if (!(c[index4] == color1 && c[index4 + 1] == color2 && c[index4 + 2] == color3 && c[index4 + 3] == 255) ||
!(c[lastIndex4] == color1 && c[lastIndex4 + 1] == color2 && c[lastIndex4 + 2] == color3 && c[lastIndex4 + 3] == 255)) {
isWrite = false
break
}
}
if (isWrite) {
checkCount++
if (checkCount >= margin * 2) {
break
}
} else {
checkCount = 0
}
}
height = Math.round(i - position) || Math.min(leftHeight, a4HeightCanvas)
if (height <= 0) {
height = a4HeightCanvas
}
} else {
height = leftHeight
}
canvas1.width = canvas.width
canvas1.height = height
console.log(index, 'height:', height, 'pos', position)
const ctx = canvas1.getContext('2d')
ctx.drawImage(result, 0, position, canvas.width, height, 0, 0, canvas.width, height)
if (position != 0) {
pdf.addPage()
}
pdf.addImage(canvas1, 'JPEG', 10, 10, a4Width, a4Width / canvas1.width * height)
leftHeight -= height
position += height
}
}
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < a4HeightCanvas) {
// const pageData = result.toDataURL('image/jpeg', 1.0)
pdf.addImage(result, 'JPEG', 10, 10, a4Width, a4Width / result.width * leftHeight)
} else {
while (leftHeight > 0) {
createImgPdf(canvas)
}
}
pdf.save(pageName + '.pdf')
// console.log(that.$createDate())
loading.close()
})
}
}
嘿嘿,大功告成!!!
最后,感谢大家阅读我的小文章,请帮我点亮一下我的小心心吧!!!