Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
html-shot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
劳工
html-shot
Commits
2a777ad9
Commit
2a777ad9
authored
Jul 05, 2021
by
rockyl
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
init
parent
7a7dcc5b
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
157 additions
and
157 deletions
+157
-157
config.ts
src/config.ts
+1
-1
dom-parser.ts
src/dom-parser.ts
+45
-45
index.ts
src/index.ts
+2
-2
toCanvas.ts
src/toCanvas.ts
+109
-109
No files found.
src/config.ts
View file @
2a777ad9
...
...
@@ -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
src/dom-parser.ts
View file @
2a777ad9
/**
* 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
}
src/index.ts
View file @
2a777ad9
...
...
@@ -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截图
...
...
src/toCanvas.ts
View file @
2a777ad9
...
...
@@ -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
()
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment