给GitBook中的SVG图像提供缩放和下载功能
文章目录
简要说明如何给GitBook
中的SVG
图像提供缩放和下载功能,实现可对复杂的SVG
图像进行缩放和下载。
背景
在项目开发流程中个人习惯采用Mermaid
中的Sequence diagrams组件通过画时序图的方式来描述复杂的业务逻辑并将其用GitBook
呈现出来便于共享阅读,类似如下
随着使用的增多发现一个问题,当用该图表来描述复杂的业务逻辑时,由于参与者和相互调用流程特别多,生成的时序图特别密集,根本无法查看,类似如下
由于Mermaid
生成的图表都是以SVG形式呈现的,而SVG
图像的特性是不管放大多少倍都不会失真,如果能通过某种方式将对应的SVG
下载下来,之后用浏览器即可进行放大缩小来查看具体的细节,则岂不是能解决此问题?
更进一步的,如果直接在页面上提供放大缩小功能,就能避免将对应SVG
文件下载到本地,使用起来岂不是更方便?而SVG
的缩放功能GitHub
上有很多已有的实现,理论分析完全可行!
实现
缩放功能
在GitHub
上对比相关的SVG
组件后,最终选定svg-pan-zoom作为SVG
功能扩展度的实现,正好自己之前改进过一个基于Mermaid
的GitBook
插件gitbook-plugin-mermaid-fox,可将svg-pan-zoom
相关的功能整合到该插件中,后续GitBook
中升级插件版本即可。
在svg-pan-zoom
的官方文档中可找到该组件的用法,十分简单
var panZoomTiger = svgPanZoom('#demo-tiger');
// or
var svgElement = document.querySelector('#demo-tiger')
var panZoomTiger = svgPanZoom(svgElement)
调用svgPanZoom
时还可根据实际情况添加各种配置
svgPanZoom('#demo-tiger', {
viewportSelector: '.svg-pan-zoom_viewport',
panEnabled: true,
controlIconsEnabled: false,
zoomEnabled: true,
dblClickZoomEnabled: true,
mouseWheelZoomEnabled: true,
preventMouseEventsDefault: true,
zoomScaleSensitivity: 0.2,
minZoom: 0.5,
maxZoom: 10,
fit: true,
contain: false,
center: true,
refreshRate: 'auto',
beforeZoom: function(){},
onZoom: function(){},
beforePan: function(){},
onPan: function(){},
onUpdatedCTM: function(){},
customEventsHandler: {},
eventsListenerElement: null
});
之后要找到Mermaid
渲染图表结束时的回调方法,在其官网上没有找到相关的说明,但通过Google发现在GitHub
上有相关说明,核心代码如下
import mermaid from '${src}';
mermaid.run({
querySelector: '.mermaid',
postRenderCallback: (id) => {
console.log(id);
}
});
很显然可通过在postRenderCallback
回调方法中添加我们自己的逻辑,相关的代码如下
mermaid.run({
querySelector: '.mermaid',
postRenderCallback: (id) => {
let ele = document.getElementById(id);
let svg = ele.getBBox();
let height = svg.height;
let aHeight = height > 800 ? 800 : height;
ele.setAttribute('style','height: '+aHeight+'px;overflow:scroll;');
let panZoomTiger = svgPanZoom('#'+id,{
zoomEnabled: true,
controlIconsEnabled: true
});
panZoomTiger.resize();
panZoomTiger.updateBBox();
}
});
至此SVG
缩放功能整合完毕,接下来需要研究下载功能。
下载功能
由于SVG
图像一般都是以源码的方式直接嵌入HTML
页面中,故其下载功能较为简单,只需要找到对应的DOM节点,通过JavaScript
相关的技术获取到其完整的内容,然后下载到本地即可。
首先自己基于网络上的相关资料找到如下代码,本地验证下载txt
时能正常工作。
function downloadURI(uri, name) {
var link = document.createElement("a");
link.download = name;
link.href = uri;
link.click();
}
但当用其测试SVG
下载时,下载的文件一直为空且浏览器也没提示任何错误,多次搜索发现Chrome出于安全考虑,默认情况下不允许通过JavaScript
下载SVG
文件,需要做一些特殊处理来规避此限制, 继续查找后找到一篇说明,其中给出了如下的解决方案,经本地测试可行。
function downloadSvg() {
var svg = document.getElementById("svg");
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg);
source = source.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
source = source.replace(/ns\d+:href/g, 'xlink:href'); // Safari NS namespace fix.
if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) {
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if (!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)) {
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}
var preface = '<?xml version="1.0" standalone="no"?>\r\n';
var svgBlob = new Blob([preface, source], { type: "image/svg+xml;charset=utf-8" });
var svgUrl = URL.createObjectURL(svgBlob);
var downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = name;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
缩放与下载功能分别验证完毕后,接下来只需将其整合到对应的GitBook
插件中即可,完整代码参见gitbook-plugin-mermaid-fox。
显示效果
添加缩放和下载按钮后后默认显示效果如下,其中下载按钮位于右上角,缩放按钮位于右下角
通过图中的按钮或鼠标滚轮放大后的效果如下,可以看出基本满足需求
相关代码
修改过程中主要涉及到index.js
和plguin.js
这两个文件,同时需要把svg-pan-zoom.js
文件放到相关目录下以方便加载
-
index.js
用于文件加载function processMermaidBlockList(page) { const mermaidRegex = /^[ \t]*```\s*mermaid[ \t]*$([^`]*(?:`[^`]+)*)```$/igm; let download='<svg width="15" height="15" viewBox="0 0 8 8" fill="#707070" xmlns="http://www.w3.org/2000/svg"><path d="m3 0v3h-2l3 3 3-3h-2v-3zm-3 7v1h8v-1z"/></svg>'; page.content = page.content.replace(mermaidRegex, '<div><div class="download" style="float:right;padding-right:35px;cursor:pointer">' +download+'</div><div class="mermaid">$1</div></div>'); return page; } module.exports = { website: { assets: 'dist', css: [ 'mermaid/mermaid.css' ], js: [ 'book/plugin.js', 'book/svg-pan-zoom.js' ] }, hooks: { 'page:before': processMermaidBlockList } };
-
plugin.js
核心代码require([ 'gitbook' ], function (gitbook) { gitbook.events.bind('page.change', function () { mermaid.run({ querySelector: '.mermaid', postRenderCallback: (id) => { let ele = document.getElementById(id); let svg = ele.getBBox(); let height = svg.height; let aHeight = height > 800 ? 800 : height; ele.setAttribute('style','height: '+aHeight+'px;overflow:scroll;'); let panZoomTiger = svgPanZoom('#'+id,{ zoomEnabled: true, controlIconsEnabled: true }); panZoomTiger.resize(); panZoomTiger.updateBBox(); let download = ele.parentNode.previousSibling; download.addEventListener('click',e => { downloadData(id, ele); }); } }); }); }); function downloadData(id,ele) { let svg = ele.cloneNode(true); // remove svg-pan-zoom-controls for the download file svg.getElementById("svg-pan-zoom-controls").remove(); let serializer = new XMLSerializer(); let source = serializer.serializeToString(svg); source = source.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace source = source.replace(/ns\d+:href/g, 'xlink:href'); // Safari NS namespace fix. if (!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)) { source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"'); } if (!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)) { source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"'); } let preface = '<?xml version="1.0" standalone="no"?>\r\n'; let svgBlob = new Blob([preface, source], { type: "image/svg+xml;charset=utf-8" }); let svgUrl = URL.createObjectURL(svgBlob); let downloadLink = document.createElement("a"); let name = id + '.svg'; downloadLink.download = name; downloadLink.href = svgUrl; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); }