-
在web中对svg图形的操作,我使用
svg.js
库挺久的了,对svg图形的拖动缩放我一直用的衍生插件
svg.panzoom.js
-
svg.panzoom库对svg拖拽缩放是对viewBox的设置来实现的,至于使用viewBox来缩放、拖拽的原理,建议各位百度了解一下,不细说了(以后有空可以讲解一下)
-
最近使用这个库拖拽svg发现了一个比较严重的性能问题(其实之前一个项目就发现了,不过当时没有这么卡顿且需求是只用同时预览一张图即可),对性能没有太大要求,便没有多管,现在这个项目是多tab页预览svg图形,对性能有一定要求,见下图:
gif图中,可以发现,拖拽异常的卡顿,这才6张svg图(实际上打开一张也卡),有时候直接拖动不了了
-
本文使用到的类和方法基本由svg.js库提供,如有不明白的地方,查看
官方文档
在以前做过渡动画时,曾经对height、width、margin等属性设置了transition,发现这样设置过渡动画,一旦元素多了后会异常的卡顿,这是因为改变这些属性时会触发
浏览器重排
大量耗费性能,而transform既不会触发重排也不会触发重绘,是交给gpu渲染,关于浏览器的重排、重绘、硬件加速的详细原理各位可以百度一下“transition 卡顿”、“浏览器重排、重绘、gpu加速”,可以看看
这篇文章
或
这篇
,我在这里就不多说了(没有深入了解)
-
直接使用现有开源库当然是最方便省事的啦,在github中找了几个start比较高的库:
svg-pan-zoom
、
panzoom
-
使用panzoom库,发现几乎不存在卡顿,并且它的平滑滚动(smoothScroll)很不错,效果如下:
-
使用svn-pan-zoom库,效果没有panzoom库好:
-
这两个库都是对g元素的transform来实现缩放的,svg-pan-zoom必须传入svg元素并且会使用一个g元素包裹svg内的所有元素;而panzoom对传入元素没有限制,但只能对传入的指定元素进行移动缩放
-
然而本方案有个致命缺点,这两个库都会自动删除svg的viewBox属性,意味着没有办法使用svg的viewBox坐标系了,而这两个库提供的pan和zoom方法都是传入基于Dom坐标系的clientX、clientY,这将影响到现有系统的业务功能(视图中移动到svg图中到指定的元素),所以此方案暂时抛弃
-
window.requestAnimationFrame可以让DOM在每一帧中集中处理DOM操作,试试这个神器能不能提高性能?
-
分别在wheelZoom、panning事件中加上requestAnimationFrame,对svg.panzoom.js代码修改如下:
-
先使用小一点的图测试实际效果:
-
使用大一点的图测试实际效果:
-
如果连续触发拖动开始、拖动结束,那么会异常的卡(黄点是鼠标按下):
-
拖动时不会卡顿了,但在浏览大图时整体流畅度依然不高(fps低),并且拖动开始和拖动结束的时候会卡顿一下,此时我还没有找到这个问题原因(见方案4)
-
参考方案1的原理,既然transform性能效果这么好,那么能不能既提高拖拽性能,又不删除svg的viewbox属性呢,想到如下方案:在panning时,仅对指定的g元素(可以把svg所有内容都放进去)transform实现拖动、缩放,在panEnd时将transform变化到svg的viewBox上,再配合window.requestAnimationFrame
if (this.panZoomAgent) {
this.panZoomAgentTransform.translateO(deltaX, deltaY)
} else {
this.viewbox = this.viewbox.transform(new Matrix().translate(-deltaX, -deltaY))
if (this.panZoomAgent && this.isPanning ) {
this.panZoomAgentTransform.scaleO(ratio, focus.x, focus.y)
} else {
this.viewbox = this.svg.viewbox().transform(new Matrix({ scale: 1 / ratio, origin: focus }))
if (!this.panZoomAgent || !this.panZoomAgentTransform) return
this.viewbox = this.viewbox.transform(this.panZoomAgentTransform.inverse())
this.panZoomAgent.node.setAttribute('transform', '')
this.panZoomAgentTransform = null
this.svg.node.setAttribute('viewBox', this.viewbox.toString())
- 为什么不保持g元素的transform,要在panEnd时对viewBox去transform呢?因为当时想到这个方案的时候,我并不想去变动svg的坐标系,因为有个功能需要定位到指定的元素,如果保持指定g元素的transform,那么svg中所有元素坐标都变了,而当时想了好久都没有想到怎么计算transform后的坐标系
- 实际效果,可以发现在mousedown(panStart)和mouseup(panEnd)时,还是会卡一下:

带上开发者工具的gif

至此,卡顿问题解决了,如果要使用方案4的话,还要解决坐标系问题来实现zoom和panTo方法,
比如一个元素的x=1180,y=1157,那么如何根据当前svg缩放、位移偏差(就是代理g元素的transform),来将此元素移动到svg图中心位置呢?可以使用svg.js提供了的类和方法来实现:Point类的transofrm方法,具体代码如下:
* 重新实现svg实例的zoom方法(当仅使用代理元素时)
* @param {number} lvl
* @param {import('@svgdotjs/svg.js').CoordinateXY} focus
* @returns
zoomTo(lvl, focus) {
if (lvl == null) return this.zoomLevel
const viewbox = this.original.viewbox
let zoomDelta = lvl / this.zoomLevel
if (!focus) {
focus = {
x: viewbox.cx,
y: viewbox.cy
let realFocus = new Point(focus).transform(this.panZoomAgentTransform)
this.panZoomAgentTransform.translateO(viewbox.cx - realFocus.x, viewbox.cy - realFocus.y).scaleO(zoomDelta, viewbox.cx, viewbox.cy)
this.zoomLevel *= zoomDelta
this.panZoomAgent.node.setAttribute('transform', this.panZoomAgentTransform.toString())
* 将指定坐标、元素移动到svg图的中心位置
* @param {Point|Element|import('@svgdotjs/svg.js').CoordinateXY} point 必须是Svg中的元素或svg的viewbox坐标系
* @param {number} zoomlvl 缩放等级
* @param {number} duration 动画的持续时间
* @returns
panTo(point, zoomlvl, duration = 500) {
if (typeof this.panTo.runner === 'number') {
window.cancelAnimationFrame(this.panTo.runner)
} else if (this.panTo.runner instanceof Runner) {
this.panTo.runner.finish()
this.panTo.runner = null
try {
const viewbox = this.original.viewbox
let element
if (point instanceof Element) {
element = point
let elBox = element.bbox()
point = {
x: elBox.cx,
y: elBox.cy
if (zoomlvl === 'auto') {
zoomlvl = 0.1 / Math.max(elBox.height / viewbox.height, elBox.width / viewbox.width)
} else if (point === 'fit-center') {
point = {
x: viewbox.cx,
y: viewbox.cy
let zoomDelta = zoomlvl / this.zoomLevel,
realPoint = new Point(point).transform(this.panZoomAgentTransform)
this.panZoomAgentTransform.translateO(viewbox.cx - realPoint.x, viewbox.cy - realPoint.y).scaleO(zoomDelta, viewbox.cx, viewbox.cy)
this.zoomLevel *= zoomDelta
if (duration > 16) {
this.panTo.runner = this.panZoomAgent.animate(duration).transform(this.panZoomAgentTransform)
} else {
this.panZoomAgent.transform(this.panZoomAgentTransform)
return this
} catch (error) {
console.error('[SVG] panTo出错: ', error)
- 想省事直接用开源库,性能:方案4≈方案1>方案3>方案2,坐标系计算:方案2=方案3>方案4>方案1
- 在方案1中删除掉viewBox后,如何移动定位到svg中指定元素,我还没有想到如何去计算坐标
- 避免在panStart、panEnd设置svg元素的class
- 真是踩了不少坑,前前后后花费了将近3-4天时间,其实如果不是在panStart、panEnd时我去设置了svg的class属性导致dom重新计算样式浪费性能,那么在实施方案2的时候已经基本满足性能需求了,可能我就不会去尝试方案3、4了
- 然而我不去实施方案3、4,我也不会发现仅仅设置一下svg的class属性竟然会导致这么大的性能损耗,并且方案3、4我在思考如何计算transform后的svg坐标系也花费了不少时间:使用viewbox方案实现的panTo只需要传入实际坐标即可,而transform方案需要计算目标坐标变化后的实际位置,再跟原始盒子大小计算translate差值
以三次panStart、panEnd来测试(方案2-4已注释设置svg的class属性代码以提升性能)
- 源码见此处
- gif截图软件:ScreenToGif
- 关键字:transform、web中硬件加速、requestAnimationFrame
- 使用svg.js也快两年了,一直想编写一篇关于svg.js的学习教程,去年就已经创建好草稿了,奈何一直没时间动笔(下次一定),最近遇到了这个问题就赶紧记录下来了

- 前端小白一个,如有不足请指出
前言在web中对svg图形的操作,我使用svg.js库挺久的了,对svg图形的拖动缩放我一直用的衍生插件svg.panzoom.jssvg.panzoom库对svg拖拽缩放的实现是对viewBox的设置来进行缩放的,至于使用viewBox来缩放、拖拽的原理,建议各位百度了解一下,不细说了(以后有空可以讲解一下)最近使用这个库拖拽svg发现了一个比较严重的性能问题(其实之前一个项目就发现了,不过当时没有这么卡顿且需求是只用同时预览一张图即可),对性能没有太大要求,便没有多管,现在这个项目是多tab页
闲来无事写了个地图控件,基于SVG。可以缩放,可拖动,可点击。SVG具有体积小,不失真的优点。而且由于保存的是路径信息,可以做到复杂图形的点击判断功能。还是很香的。
原理,SVG 意为可缩放矢量图形(Scalable Vector Graphics)。
SVG 使用 XML 格式定义图像。在xml中定义了路径,只需要将路径解析保存到path中。再绘制出来就行了。
svg地图的获取
使用如下地址(需要科学上网)
Pixel Map
首页是这样的,支持世界上所有国家
下载中国地图
svg元素不会太多,否则会造成卡顿。
最近有个项目需要我帮一下前端,主要是使用d3绘制svg放在页面,其中有一个功能就是对绘制的svg进行拖动和缩放,有点像地图。
这里我已经写好了一个方法来绘制svg
function drawTopo(svg, data, x, y, svgA, radio)...
这段时间比较忙,php的设计模式想写工厂模式,但是比较难写,不是一时半会儿能写完的,先把现在正在使用的关于svg中元素的拖动,记录一下。
借助svg,我们可以画出多种多样的图形,而且利用g标签,还可以把多个标签组合在一起,让他们具有相同的行为。语法也比较简洁,希望还么有接触过的同学,学习一下。
要实现拖动功能(大神绕行…),我们需要给元素定义mousedown(响应鼠标在元素范围按住),