Skip to content

Commit 33af046

Browse files
committed
feat: export svg
1 parent 0ddf854 commit 33af046

2 files changed

Lines changed: 77 additions & 17 deletions

File tree

src/pages/home/index.jsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ import {
8787
oneApiImage,
8888
} from '@utils/aidFn'
8989
import { fireConfetti } from '@utils/confetti'
90+
import exportSvg from '@utils/svgExport'
9091
import Zoom from 'react-medium-image-zoom'
9192
import 'react-medium-image-zoom/dist/styles.css'
9293
import styles from './index.module.less'
@@ -357,23 +358,9 @@ const Home = () => {
357358

358359
// 使用 useRef 来引用 SVG 元素
359360
const svgRef = useRef(null)
361+
const [svgFileName, setSvgFileName] = useState('mySvg.svg')
360362
const saveSvgAsFile = () => {
361-
if (svgRef.current) {
362-
// 获取 SVG 元素的 outerHTML
363-
const svgContent = svgRef.current.outerHTML
364-
// 创建 Blob 对象
365-
const blob = new Blob([svgContent], { type: 'image/svg+xml' })
366-
// 创建下载链接
367-
const url = URL.createObjectURL(blob)
368-
// 创建 <a> 元素
369-
const a = document.createElement('a')
370-
a.href = url
371-
a.download = 'mySvg.svg'
372-
// 模拟点击下载链接
373-
a.click()
374-
// 释放临时 URL
375-
URL.revokeObjectURL(url)
376-
}
363+
exportSvg(svgRef.current, svgFileName)
377364
}
378365

379366
const mcRef = useRef(null)
@@ -669,9 +656,14 @@ const Home = () => {
669656
width: 360,
670657
}}
671658
>
659+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
660+
<Input value={svgFileName} onChange={(e) => setSvgFileName(e.target.value)} style={{ width: 180 }} />
661+
<Button type="primary" onClick={saveSvgAsFile}>
662+
保存 SVG
663+
</Button>
664+
</div>
672665
<section className="relative p-4">
673666
<div className={styles.itemCircle} />
674-
<button onClick={saveSvgAsFile}>保存 SVG</button>
675667
<svg ref={svgRef} style={{ height: '10px', width: '100%' }}>
676668
<path d="M 0 0 L 5000 0" fill="none" stroke="#595959">
677669
<animate

src/utils/svgExport.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
export function exportSvg(svgElement, fileName = 'mySvg.svg') {
2+
if (!svgElement) return false
3+
4+
const clone = svgElement.cloneNode(true)
5+
6+
if (!clone.hasAttribute('xmlns')) {
7+
clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
8+
}
9+
10+
const inlineImportantStyles = (el) => {
11+
try {
12+
const cs = window.getComputedStyle(el)
13+
const props = [
14+
'stroke',
15+
'stroke-width',
16+
'stroke-linecap',
17+
'stroke-linejoin',
18+
'fill',
19+
'opacity',
20+
'stroke-dasharray',
21+
'stroke-dashoffset',
22+
'color',
23+
'font-size',
24+
]
25+
props.forEach((p) => {
26+
const v = cs.getPropertyValue(p)
27+
if (v) el.setAttribute(p, v)
28+
})
29+
} catch (e) {
30+
// ignore
31+
}
32+
}
33+
34+
;[clone, ...Array.from(clone.querySelectorAll('*'))].forEach(inlineImportantStyles)
35+
36+
const rect = svgElement.getBoundingClientRect()
37+
const width = clone.getAttribute('width') || rect.width || 300
38+
const height = clone.getAttribute('height') || rect.height || 150
39+
clone.setAttribute('width', width)
40+
clone.setAttribute('height', height)
41+
42+
if (!clone.hasAttribute('viewBox')) {
43+
try {
44+
const bbox = clone.getBBox()
45+
if (bbox && bbox.width && bbox.height) {
46+
clone.setAttribute('viewBox', `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`)
47+
} else {
48+
clone.setAttribute('viewBox', `0 0 ${width} ${height}`)
49+
}
50+
} catch (e) {
51+
clone.setAttribute('viewBox', `0 0 ${width} ${height}`)
52+
}
53+
}
54+
55+
const svgContent = new XMLSerializer().serializeToString(clone)
56+
const blob = new Blob([svgContent], { type: 'image/svg+xml;charset=utf-8' })
57+
const url = URL.createObjectURL(blob)
58+
const a = document.createElement('a')
59+
a.href = url
60+
a.download = fileName || 'mySvg.svg'
61+
document.body.appendChild(a)
62+
a.click()
63+
a.remove()
64+
URL.revokeObjectURL(url)
65+
return true
66+
}
67+
68+
export default exportSvg

0 commit comments

Comments
 (0)