javascript - 加载附加的 SVG 文件中包含的 javascript 库
问题描述
我正在尝试构建一个基于浏览器的 SVG 光栅器。SVG 文件可以包含影响输出的 javascript 代码(例如,随机更改元素的颜色)并使用库(例如chroma.js)来执行此操作。
我试图实现的过程是:
- 加载 SVG 文件
- 加载 SVG 文件中链接的库
- 执行脚本标签中包含的 javascript
- 显示在画布上
- 将画布保存为 PNG
这是必要的,因为将 SVG 附加到 HTML 元素不会运行 SVG 中包含的 JS。
一切正常 - 除了加载外部库(第 2 点)。执行此操作的代码段如下
$.get('/some_svg_file.svg', function(d) {
// this will be replaced with file drag/drop later
var getLibs = Array.from(d.querySelectorAll('script'))
.filter(p => p.getAttribute('xlink:href'))
.map(p => {
return axios.get(p.getAttribute('xlink:href'))
})
// only run embedded js after libraries have been downloaded
Promise.all(getLibs).then((values) => {
values.forEach(d => {
try {
eval(d.data);
} catch (e) {
console.log(e)
}
});
Array.from(d.querySelectorAll('script'))
.forEach(p => {
if (p.textContent.length) {
try {
eval(p.textContent)
} catch (e) {
console.log(e)
}
}
})
// code that writes to canvas etc goes here
})
})
因此,如果我将链接 chroma.js 放在 html 文件头中,一切正常。如果我下载它eval
,SVG文件中的脚本会失败ReferenceError: chroma is not defined
我做的另一个尝试是使用脚本标记技术,如下所示:
$.get('/foo_with_js.svg', function(d) {
var getLibs = Array.from(d.querySelectorAll('script'))
.filter(p => p.getAttribute('xlink:href'))
.map(p => {
var s = document.createElement('script');
s.src = p.getAttribute('xlink:href');
s.type = "text/javascript";
s.async = false;
document.querySelector('head').appendChild(s);
})
// load scripts and save to canvas
})
它也以同样的方式失败(尽管 chroma.js 被整齐地包含在 head 部分中。
那么我怎样才能让它工作 - 以便我可以加载 SVG,将其附加到 HTML 并在其中运行脚本,而无需预先链接 HTML 中的所有可能脚本?
啊 - 如果你问为什么不使用其他转换过程,答案是“缺乏对 SVG 过滤器的一致支持”
解决方案
如果您可以使用嵌入在 SVG 中的任何JavaScript 库使其工作,这是一个很好的问题,但这里我有一个示例,我按照您的建议使用chroma.js 。
对 SVG 文档的引用被添加到<object>
. 这里是静态的,但我想它可以动态更新。现在 SVG 正在做它的事情,比如<rect>
在<circle>
. 在任何时候,我都可以获取 的 contentDocument (contentDocument.documentElement.outerHTML) <object>
,即特定点的 SVG 文档。如果文档中的值发生变化,这将影响 contentDocument。现在我可以获取字符串 (outerHTML) 并将其转换为数据 URI。数据 URI 可以设置为图像对象上的 src,并且图像可以在<canvas>
元素。在outerHTML中你可以看到还有对JavaScript库和我写的JavaScript代码的引用,但是当渲染成<canvas>
.
SVG 文档:
<?xml version="1.0"?>
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="drop-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feoffset in="blur" dx="4" dy="4" result="offsetBlur"/>
<feMerge>
<feMergeNode in="offsetBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<script href="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.2/chroma.min.js"/>
<rect x="0" y="0" width="200" height="200" />
<circle cx="40" cy="40" r="20" fill="blue" style="filter: url(#drop-shadow);"/>
<script>
// <![CDATA[
var cy = 40;
var speed = 5;
document.querySelector('rect').style.fill = chroma('hotpink');
setInterval(function(){
if(cy > 160 || cy < 40) speed *= -1;
cy += speed;
document.querySelector('circle').attributes['cy'].value = cy;
}, 500);
// ]]>
</script>
</svg>
HTML 文档:
<!DOCTYPE html>
<html>
<head>
<title>SVG</title>
<script type="text/javascript">
var object, canvas, img, loop;
document.addEventListener('DOMContentLoaded', e => {
object = document.querySelector('object');
canvas = document.querySelector('canvas');
img = new Image();
img.addEventListener('load', e => {
canvas.getContext('2d').drawImage(e.target, 0, 0);
});
object.addEventListener('load', e => {
loop = setInterval(function(){
var svg64 = btoa(e.target.contentDocument.documentElement.outerHTML);
var b64Start = 'data:image/svg+xml;base64,';
img.src = b64Start + svg64;
}, 500);
});
});
</script>
</head>
<body>
<object type="image/svg+xml" width="200" height="200" data="test.svg"></object>
<canvas width="200" height="200"></canvas>
</body>
</html>
这两个文件需要从服务器运行,所以不能直接从文件系统运行。
根据我的测试,SVG 动画和CSS 动画在此设置中不起作用。只有 JavaScript 中用于操作 DOM 的“动画”才有效。
推荐阅读
- javascript - 只执行一次“onscroll”功能
- reactjs - 扩展 React.Component 与扩展组件
- r - 在 Mac Catallina 上安装 RMySQL 包
- javascript - 在一行上重命名导入数组的键(很像解构)
- javascript - 我无法在 React 中将道具从父母传递给孩子
- linux - 如何修复 fglrun:符号查找错误:/u2/acct/d84-lc/obj/4js/lib/userextension.so:未定义符号:fglcapi_deccvasc
- conda - conda build 是否支持构建软件包的调试版本?
- sql - 在 Impala 中选择列值只有数字的行
- c++ - 并发模型 C++
- c - Linux C ext2fs_write_inode_full 无法写入