Commit 2a777ad9 authored by rockyl's avatar rockyl

init

parent 7a7dcc5b
......@@ -2,4 +2,4 @@
* Created by rockyl on 2021/1/26.
*/
export const debugMode = window['html-shot/debug-mode'] || false;
export const debugMode = window['html-shot/debug-mode'] || false
/**
* Created by rockyl on 2021/1/11.
*/
import {debugMode} from "./config.js";
import {debugMode} from "./config.js"
const commonStyleKeys = [
'backgroundColor',
......@@ -26,15 +26,15 @@ const includeStyleKeys = [
]
export function parseDom(el: HTMLElement = document.body) {
const {left: pX, top: pY, width, height} = el.getBoundingClientRect();
const {left: pX, top: pY, width, height} = el.getBoundingClientRect()
let nodes = [];
let nodes = []
walkNode(el, function (childNode) {
let vNode, bound, node, isText;
let skip = false;
let vNode, bound, node, isText
let skip = false
switch (childNode.nodeName) {
case 'IMG':
node = childNode;
node = childNode
const src = node.getAttribute('src') //必须使用此方法,使用node.src且为空时会返回当前页面链接
if (src) { //过滤src为空的图片
vNode = {
......@@ -42,46 +42,46 @@ export function parseDom(el: HTMLElement = document.body) {
src,
}
}
break;
break
case 'CANVAS':
node = childNode;
node = childNode
vNode = {
type: 3,
img: node,
}
break;
break
case '#text':
isText = true;
node = childNode.parentElement;
let text = childNode.data.trim();
isText = true
node = childNode.parentElement
let text = childNode.data.trim()
if (text) { //过滤空文本
let range = document.createRange();
range.selectNode(childNode);
bound = range.getBoundingClientRect();
range.detach();
let range = document.createRange()
range.selectNode(childNode)
bound = range.getBoundingClientRect()
range.detach()
vNode = {
type: 1,
text,
}
}
break;
break
case '#comment':
skip = true;
break;
skip = true
break
default:
break;
break
}
if (skip) {
return;
return
}
if (!bound && childNode.getBoundingClientRect) {
bound = childNode.getBoundingClientRect();
bound = childNode.getBoundingClientRect()
}
let styles = getStylesWithoutDefaults(node || childNode, includeStyleKeys);
let styles = getStylesWithoutDefaults(node || childNode, includeStyleKeys)
if (!isText) {
for (let skey of commonStyleKeys) {
......@@ -95,64 +95,64 @@ export function parseDom(el: HTMLElement = document.body) {
}
}
if (styles[skey] !== undefined) {
vNode[skey] = styles[skey];
vNode[skey] = styles[skey]
}
}
}
if (vNode && bound) {
const {left, top, width, height} = bound;
vNode.x = left - pX;
vNode.y = top - pY;
vNode.width = width;
vNode.height = height;
const {left, top, width, height} = bound
vNode.x = left - pX
vNode.y = top - pY
vNode.width = width
vNode.height = height
for (let skey in styles) {
if (styleKeys.indexOf(skey) < 0) {
continue
}
vNode[skey] = styles[skey];
vNode[skey] = styles[skey]
}
nodes.push(vNode);
nodes.push(vNode)
}
});
})
if (debugMode) {
console.info(nodes);
console.info(nodes)
}
return {
width,
height,
nodes,
};
}
}
function walkNode(root, callback) {
callback(root);
callback(root)
for (let i = 0, li = root.childNodes.length; i < li; i++) {
const childNode = root.childNodes[i];
walkNode(childNode, callback);
const childNode = root.childNodes[i]
walkNode(childNode, callback)
}
}
function getStylesWithoutDefaults(element, includes: string[] = []): any {
let dummy = document.createElement('element-' + (new Date().getTime()));
document.body.appendChild(dummy);
let dummy = document.createElement('element-' + (new Date().getTime()))
document.body.appendChild(dummy)
let defaultStyles = getComputedStyle(dummy);
let elementStyles = getComputedStyle(element);
let defaultStyles = getComputedStyle(dummy)
let elementStyles = getComputedStyle(element)
let diff = {};
let diff = {}
for (let key in elementStyles) {
if (includes.indexOf(key) >= 0 || (elementStyles.hasOwnProperty(key) && defaultStyles[key] !== elementStyles[key])) {
diff[key] = elementStyles[key];
diff[key] = elementStyles[key]
}
}
dummy.remove();
dummy.remove()
return diff;
return diff
}
......@@ -2,8 +2,8 @@
* Created by rockyl on 2021/1/11.
*/
import {parseDom} from "./dom-parser.js";
import {RenderOptions, toCanvas} from "./toCanvas.js";
import {parseDom} from "./dom-parser.js"
import {RenderOptions, toCanvas} from "./toCanvas.js"
/**
* HTML截图
......
......@@ -61,33 +61,33 @@ export interface RenderOptions {
* @return Promise<HTMLCanvasElement | string>
*/
export async function toCanvas(data: ICData, options: RenderOptions = {}, callback?: (canvas: HTMLCanvasElement) => void): Promise<HTMLCanvasElement | string> {
const {type: exportType = 'png', quality = 0.7} = options;
let {scale = window['devicePixelRatio'] || 1} = options;
const {type: exportType = 'png', quality = 0.7} = options
let {scale = window['devicePixelRatio'] || 1} = options
let {nodes, width, height} = data;
width *= scale;
height *= scale;
let {nodes, width, height} = data
width *= scale
height *= scale
let canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext("2d");
let canvas = document.createElement("canvas")
canvas.width = width
canvas.height = height
let ctx = canvas.getContext("2d")
//先加载完所有图片
let p: Promise<void>[] = [];
let p: Promise<void>[] = []
nodes.forEach((n) => {
if (n.type == NodeType.IMAGE) {//图片标签
p.push(
new Promise((resolve, reject) => {
let img = new Image();
let img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
n.img = img;
n.img = img
resolve()
}
img.onerror = () => {
reject(`html shot error: can't fetch image[${img.src}]`);
reject(`html shot error: can't fetch image[${img.src}]`)
}
img.src = n.src;
img.src = n.src
})
)
}
......@@ -95,18 +95,18 @@ export async function toCanvas(data: ICData, options: RenderOptions = {}, callba
if (n.backgroundImage) {
p.push(
new Promise((resolve, reject) => {
let img = new Image();
let img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
n.imgBg = img;
n.imgBg = img
resolve()
}
img.onerror = () => {
reject(`html shot error: can't fetch image[${img.src}]`);
reject(`html shot error: can't fetch image[${img.src}]`)
}
let result = n.backgroundImage.match(/url\((.*)*\)/);
let result = n.backgroundImage.match(/url\((.*)*\)/)
if (result) {
img.src = result[1].replace(/['"]/g, '');
img.src = result[1].replace(/['"]/g, '')
} else {
reject(`html shot error: can't parse ${n.backgroundImage}`)
}
......@@ -114,13 +114,13 @@ export async function toCanvas(data: ICData, options: RenderOptions = {}, callba
)
}
})
if (p.length) await Promise.all(p);
if (p.length) await Promise.all(p)
nodes.forEach((n) => {
//通用属性先绘制,背景颜色,边框等等
//背景颜色
if (n.backgroundColor) drawBackgroundColor(n, ctx, scale)
//背景图片
if (n.backgroundImage) ctx.drawImage(n.imgBg, n.x * scale, n.y * scale, n.width * scale, n.height * scale);
if (n.backgroundImage) ctx.drawImage(n.imgBg, n.x * scale, n.y * scale, n.width * scale, n.height * scale)
//边框
if (n.borderWidth) drawBorder(n, ctx, scale)
......@@ -135,12 +135,12 @@ export async function toCanvas(data: ICData, options: RenderOptions = {}, callba
}
})
let result: any = canvas;
let result: any = canvas
switch (exportType) {
case 'jpeg':
case 'png':
result = canvas.toDataURL('image/' + exportType, quality);
break;
result = canvas.toDataURL('image/' + exportType, quality)
break
}
callback && callback(result)
......@@ -154,20 +154,20 @@ export async function toCanvas(data: ICData, options: RenderOptions = {}, callba
* @param scale
*/
function drawImage(data: INodeData, ctx: CanvasRenderingContext2D, scale: number) {
let {x, y, img, width, height, borderRadius: borderRadiusTxt} = data;
x *= scale;
y *= scale;
width *= scale;
height *= scale;
let borderRadius = parseFloat(borderRadiusTxt) * scale;
let {x, y, img, width, height, borderRadius: borderRadiusTxt} = data
x *= scale
y *= scale
width *= scale
height *= scale
let borderRadius = parseFloat(borderRadiusTxt) * scale
if (borderRadius) {//有圆角,画遮罩,暂时只管一个
ctx.save();
ctx.save()
borderRadiusPath(data, ctx, scale)
ctx.clip();
ctx.clip()
}
ctx.drawImage(img, x, y, width, height)
if (borderRadius) ctx.restore();
if (borderRadius) ctx.restore()
}
/**
......@@ -186,58 +186,58 @@ function drawText(data: INodeData, ctx: CanvasRenderingContext2D, scale: number)
fontStyle,
wordWrap,
textAlign
} = data;
} = data
x *= scale;
y *= scale;
width *= scale;
height *= scale;
let fontSize = parseFloat(fontSizeTxt) * scale;
x *= scale
y *= scale
width *= scale
height *= scale
let fontSize = parseFloat(fontSizeTxt) * scale
let font = fontSize + 'px';
let font = fontSize + 'px'
font += " Arial";//字体没有
//font-weight:bold;font-style:italic;
if (fontWeight) font = fontWeight + " " + font;
if (fontStyle) font = fontStyle + " " + font;
//font-weight:bold;font-style:italic
if (fontWeight) font = fontWeight + " " + font
if (fontStyle) font = fontStyle + " " + font
ctx.font = font;
ctx.textBaseline = "bottom";
ctx.fillStyle = color||"black";
var textMetrics = ctx.measureText(text);
let widthAll = textMetrics.width;
ctx.font = font
ctx.textBaseline = "bottom"
ctx.fillStyle = color||"black"
var textMetrics = ctx.measureText(text)
let widthAll = textMetrics.width
// var actualBoundingBoxAscent = textMetrics.actualBoundingBoxAscent;
// var actualBoundingBoxAscent = textMetrics.actualBoundingBoxAscent
// console.log(actualBoundingBoxAscent)
// console.log(ctx.measureText(text))
//超过宽度需要换行,且需要用到居中方式
if (wordWrap == "break-word" && widthAll > width) {
let realLines = [];
let w = ctx.measureText(text[0]).width;
let lineStr = text[0];
let wordW = 0;
let strLen = text.length;
let realLines = []
let w = ctx.measureText(text[0]).width
let lineStr = text[0]
let wordW = 0
let strLen = text.length
for (let j = 1; j < strLen; j++) {
wordW = ctx.measureText(text[j]).width;
w += wordW;
wordW = ctx.measureText(text[j]).width
w += wordW
if (w > width) {
realLines[realLines.length] = lineStr;
lineStr = text[j];
w = wordW;
realLines[realLines.length] = lineStr
lineStr = text[j]
w = wordW
} else {
lineStr += text[j];
lineStr += text[j]
}
}
//最后一行
realLines[realLines.length] = lineStr;
ctx.textAlign = textAlign || "left";
let tx = 0;
realLines[realLines.length] = lineStr
ctx.textAlign = textAlign || "left"
let tx = 0
if (ctx.textAlign == "center") {
tx = width * 0.5;
tx = width * 0.5
} else if (ctx.textAlign == "right") {
tx = width;
tx = width
}
//有待考虑.现在直接拿高度取平均算每行高度
let lineH = height / realLines.length;
let lineH = height / realLines.length
realLines.forEach((r, i) => {
ctx.fillText(r, x + tx, y + i * lineH + lineH)
})
......@@ -253,17 +253,17 @@ function drawBackgroundColor(data: INodeData, ctx: CanvasRenderingContext2D, sca
x, y, width, height,
backgroundColor,
borderRadius: borderRadiusTxt
} = data;
x *= scale;
y *= scale;
width *= scale;
height *= scale;
} = data
x *= scale
y *= scale
width *= scale
height *= scale
ctx.fillStyle = backgroundColor;
let borderRadius = parseFloat(borderRadiusTxt) * scale;
ctx.fillStyle = backgroundColor
let borderRadius = parseFloat(borderRadiusTxt) * scale
if (borderRadius) {//有圆角,画遮罩,暂时只管一个
borderRadiusPath(data, ctx, scale)
ctx.fill();
ctx.fill()
} else {
ctx.fillRect(x, y, width, height)
}
......@@ -276,21 +276,21 @@ function drawBorder(data: INodeData, ctx: CanvasRenderingContext2D, scale: numbe
borderRadius: borderRadiusTxt,
borderWidth: borderWidthTxt,
borderColor
} = data;
let borderRadius = parseFloat(borderRadiusTxt) * scale;
let borderWidth = parseFloat(borderWidthTxt) * scale;
x *= scale;
y *= scale;
width *= scale;
height *= scale;
ctx.lineWidth = borderWidth;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.miterLimit = 0;
ctx.strokeStyle = borderColor;
} = data
let borderRadius = parseFloat(borderRadiusTxt) * scale
let borderWidth = parseFloat(borderWidthTxt) * scale
x *= scale
y *= scale
width *= scale
height *= scale
ctx.lineWidth = borderWidth
ctx.lineCap = "round"
ctx.lineJoin = "round"
ctx.miterLimit = 0
ctx.strokeStyle = borderColor
if (borderRadius) {
borderRadiusPath(data, ctx, scale)
ctx.stroke();
ctx.stroke()
} else {
ctx.strokeRect(x, y, width, height)
}
......@@ -300,30 +300,30 @@ function drawBorder(data: INodeData, ctx: CanvasRenderingContext2D, scale: numbe
function borderRadiusPath(data: INodeData, ctx: CanvasRenderingContext2D, scale: number) {
let {
x, y, width, height, borderRadius: borderRadiusTxt
} = data;
x *= scale;
y *= scale;
width *= scale;
height *= scale;
} = data
x *= scale
y *= scale
width *= scale
height *= scale
let borderRadius = parseFloat(borderRadiusTxt) * scale;
let borderRadius = parseFloat(borderRadiusTxt) * scale
if (borderRadius) {//有圆角,画遮罩,暂时只管一个
let max = (width < height ? width : height) / 2;
let radius = Math.min(borderRadius, max);
// ctx.beginPath();
// ctx.moveTo(x, y + radius);
// ctx.quadraticCurveTo(x, y, x + radius, y);
// ctx.lineTo(x + width - radius, y);
// ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
// ctx.lineTo(x + width, y + height - radius);
// ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
// ctx.lineTo(x + radius, y + height);
// ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
// ctx.lineTo(x, y + radius);
// ctx.clip();
ctx.beginPath();
ctx.moveTo(x + width - radius, y);
let max = (width < height ? width : height) / 2
let radius = Math.min(borderRadius, max)
// ctx.beginPath()
// ctx.moveTo(x, y + radius)
// ctx.quadraticCurveTo(x, y, x + radius, y)
// ctx.lineTo(x + width - radius, y)
// ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
// ctx.lineTo(x + width, y + height - radius)
// ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
// ctx.lineTo(x + radius, y + height)
// ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
// ctx.lineTo(x, y + radius)
// ctx.clip()
ctx.beginPath()
ctx.moveTo(x + width - radius, y)
ctx.arcTo(x + width, y, x + width, y + radius, radius)
ctx.lineTo(x + width, y + height - radius)
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius)
......@@ -331,6 +331,6 @@ function borderRadiusPath(data: INodeData, ctx: CanvasRenderingContext2D, scale:
ctx.arcTo(x, y + height, x, y + height - radius, radius)
ctx.lineTo(x, y + radius)
ctx.arcTo(x, y, x + radius, y, radius)
ctx.closePath();
ctx.closePath()
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment