彻底理解SVG图片缩放与位移的原理 2

一、SVG中拖拉的概念

我们试着用一张图来说明整个svg画布在拖移的概念

将svg画布从左边拖到右边

拖拽的过程,其实知识 viewBox 中 x-min 的改变。但为了要计算 x-min 应该要给它多少,我们会需要几个连续的步骤。我用下面这张图来说明要进行的步骤,图形当中的数字标示是步骤的顺序:

  1. 为了之后坐标系统的转换,我们要先取当前svg元素的 viewBox 值,假设一开始的值是 (x, y, w, h),这里我们只需要关注 x,简称 viewBoxX0
  2. 通过上一篇 - 彻底理解SVG图片缩放与位移的原理 1 所说明的相关内容,此时我们可以去的鼠标点击下去时的 clientX 的值,简称为 clientX0,在上图中对应到的值是10.
  3. 我们可以把 clientX 的值转成 svg 坐标系统中的值,简称为 svgX0,这里对应的是值是20
  4. 按住鼠标拖动画布,在移动的过程中可以得到当前鼠标坐标的 clientX 值,简称为 clientX1,这里对应的值是20
  5. 同样,我们可以把 clientX 的值转成 svg 坐标系统中的值,简称为 svgX1,这里对应的值是40.
  6. 我们可以计算鼠标在拖拉时,在svg坐标系统中的位移(svgX0 - svgX1),这里就会是 (20 - 40)
  7. 我们可以得到最终 viewBox x要带入的值就是 viewBoxX0 + (xvgX0 - svgX1),也就是 viewBoxX0+(20-40),从这个意义上来说,就是将svg的坐标系统整个的向右移动了20个单位,所以整个svg的坐标就向右移动了20个单位。

同理,我们也可以知道,如果进行上下拖拉的话, viewBox 的 y 会改变,而最终的 viewBox 中 y 值就是 viewBoxY0 + (svgY0 - svgY1)

转成js代码如下:

js 复制代码
/**  
开始:鼠标拖拉的效果
**/

let moving
//  鼠标点下,开始拖拉
function mouseDown(e){
  moving = true
}

// 拖拉的移动过程
function drag(e){
  if(moving === true){
     // 1. 取得一开始的 viewBox 值,原本是字符串,分割成阵列,方便后面计算
    let startViewBox = svg.getAttribute('viewBox').split(' ').map( n => parseFloat(n))

    //  2. 取得鼠标当前 viewport 中 client 坐标值
    let startClient = {
      x: e.clientX,
      y: e.clientY
    }

    //  3. 计算对应的 SVG 坐标值
    let newSVGPoint = svg.createSVGPoint()
    let CTM = svg.getScreenCTM()
    newSVGPoint.x = startClient.x
    newSVGPoint.y = startClient.y
    let startSVGPoint = newSVGPoint.matrixTransform(CTM.inverse())
    
    //  4. js拖拽后鼠标所在的 viewport client 坐标值
    let moveToClient = {
      x: e.clientX + e.movementX, //  movement 可以取得鼠标位移量
      y: e.clientY + e.movementY
    }
    
    //  5. 计算对应回去的 SVG 坐标值
    newSVGPoint = svg.createSVGPoint()
    CTM = svg.getScreenCTM()
    newSVGPoint.x = moveToClient.x
    newSVGPoint.y = moveToClient.y
    let moveToSVGPoint = newSVGPoint.matrixTransform(CTM.inverse())
    
    //  6. 计算位移量
    let delta = {
      dx: startSVGPoint.x - moveToSVGPoint.x,
      dy: startSVGPoint.y - moveToSVGPoint.y
    }
    
    //  7. 设定新的 viewBox 值
    let moveToViewBox = `${startViewBox[0] + delta.dx} ${startViewBox[1] + delta.dy} ${startViewBox[2]} ${startViewBox[3]}` 
    svg.setAttribute('viewBox', moveToViewBox)
    console.log(moveToViewBox)
  }
}
//  鼠标点击结束(拖拽結束)
function mouseUp(){
    moving = false
    showViewBox()
} //  整个过程结束


// 在svg元素上监听事件
svgElement.addEventListener('mousedown', mouseDown, false)
svgElement.addEventListener('mousemove', drag, false)
svgElement.addEventListener('mouseup', mouseUp, false)
  

二、SVG 中缩放的概念

缩放的做法,其实同样是通过改变 viewBox 的设定值,只是上面改变的是 <min-x><min-y>,缩放要改变的是 <width><height>

另外,上一篇文中提到的一个很重要的概念,就是通过 viewBox 来缩放时,实际上它是从整个 svg 元素的左上角(下图中橘色的点)进行缩放:

我们通过下面一张图可以更清楚的看到为什么在缩放的过程中,一开始的圆点为什么会跑到那个位置。为了简化理解,我们还是先只看 x 方向。

如图所示:在一开始的时候,svg 坐标系统中的1单位等于 viewport 坐标系统中的 1px,圆点对应过去的 clientX是10,将坐标系统转换后会得到 svg的x是20;在缩放的过程中,会以svg元素的左上角为原点进行缩放;缩放后,整个svg坐标系统的单位尺寸就改变了,在这里因为我们放大了两倍,所以svg坐标系统的1单位就会变成viewport坐标系统的2px,但是圆点的svg x 仍然是20。

这是因为:

  1. 以svg左上角原点做缩放(这个点其实就是当时 viewBox的min-xmin-y 的值)
  2. svg坐标系统对应会viewport坐标系统的单位尺寸已经改变。

所以,这个圆点在放大的过程中,被往右下方移动了(放大两倍是利用让viewBox的 width/2 和 height/2),这个情况会让我们点击的点,在缩放过程中相当于 viewport Client的坐标值会一直在改变,如下图中的圆圈1。 为了不让使用者在缩放时,觉得我们点击的那个店距离我们越来越远,所以我们在缩放完后要再把这个点搬回使用者点击的那个起始点,让使用者感觉这个点看起来像是在原地缩放,如下图中的圆圈2。

了解了这个概念后,我们可以尝试把步骤列出来,整个流程会如下图所示:

  1. 取得一开始的viewBox,这样我们就知道缩放的依据,简称viewBox0,其中的width简称为 viewBox0.width。
  2. 取得鼠标执行缩放位置的viewport client坐标,并取得svg坐标,简称 clientX0和svgX0
  3. 进行缩放,如果让原本的尺寸缩放2倍的话,width和height就要除以2;假设 r 为我们缩放的倍率,缩放后的viewBox为 viewBox1;所以 viewBox1.width = viewBox1.width / r,viewBox1.height = viewBox1.height / r。
  4. 将一开始鼠标执行缩放位置的viewport client坐标(也就是 clientX1),转为为对应的svg坐标,简称svgX1.
  5. 取得在缩放过程中该圆点的位移量:svgX0 - svgX1
  6. 得到最终的viewBox2: viewBoxX2 = viewBoxX0 + (svgX0 - svgX1),同理:viewBoxY2 = viewBoxY0 + (svgY0 - svgY1)

转成js代码如下:

js 复制代码
/*  
开始:鼠标缩放的效果
*/
function zoom(e){
    //  1.取得一卡死的 viewBox。
    let startViewBox = svg.getAttribute('viewBox').split(' ').map( n => parseFloat(n))
    
    //  2.取得鼠标执行缩放位置的 viewPort Client 坐标,並利用 CTM 取得 SVG 对应的坐标。
    
    //  2.1 取得鼠标执行缩放的位置
    let startClient = {
      x: e.clientX,
      y: e.clientY
    }

    //  2.2 转换成 SVG 坐标系统中的 SVG 坐标点
    let newSVGPoint = svg.createSVGPoint()
    let CTM = svg.getScreenCTM()
    newSVGPoint.x = startClient.x
    newSVGPoint.y = startClient.y
    let startSVGPoint = newSVGPoint.matrixTransform(CTM.inverse())
    
    
    //  3.进行缩放,如果要让原本的尺寸缩放两倍的话。
    //  3.1 设定缩放倍率
    let r 
    if (e.deltaY > 0) {
      r = 0.9
    } else if (e.deltaY < 0) {
      r = 1.1
    } else {
      r = 1
    }
    //  3.2 進行缩放
    svg.setAttribute('viewBox', `${startViewBox[0]} ${startViewBox[1]} ${startViewBox[2] * r} ${startViewBox[3] * r}`)
    
    //  4.将一开始鼠标的执行缩放位置的 viewPort Client 坐标利用新的 CTM ,转换出对应的 SVG 坐标。
    CTM = svg.getScreenCTM()
    let moveToSVGPoint = newSVGPoint.matrixTransform(CTM.inverse())
    
    //  5.取得在缩放过程中该圆点的位移量 `(svgX0 - svgX1)`。
    let delta = {
      dx: startSVGPoint.x - moveToSVGPoint.x,
      dy: startSVGPoint.y - moveToSVGPoint.y
    }
    
    //  6.设定最终的 viewBox2 
    let middleViewBox = svg.getAttribute('viewBox').split(' ').map( n => parseFloat(n))
    let moveBackViewBox = `${middleViewBox[0] + delta.dx} ${middleViewBox[1] + delta.dy} ${middleViewBox[2]} ${middleViewBox[3]}` 
    svg.setAttribute('viewBox', moveBackViewBox)
    
    //  更新 viewBox 
    showViewBox()
} //  結束


// 监听事件

svgElement.addEventListener('wheel', zoom, false)

理解了svg缩放和位移的原理后,我们就能够更好的使用svg图片。

相关推荐
明远湖之鱼3 天前
opentype.js 使用与文字渲染
前端·svg·字体
wsWmsw4 天前
[译] 浏览器里的 Liquid Glass:利用 CSS 和 SVG 实现折射
前端·css·svg
CodeCraft Studio5 天前
CAD文件处理控件Aspose.CAD教程:在 Python 中将 SVG 转换为 PDF
开发语言·python·pdf·svg·cad·aspose·aspose.cad
红烧code16 天前
【Rust GUI开发入门】编写一个本地音乐播放器(4. 绘制按钮组件)
rust·gui·svg·slint
吃饺子不吃馅17 天前
AntV X6图编辑器如何实现切换主题
前端·svg·图形学
吃饺子不吃馅18 天前
深感一事无成,还是踏踏实实做点东西吧
前端·svg·图形学
吃饺子不吃馅20 天前
AntV X6 核心插件帮你飞速创建画布
前端·css·svg
吃饺子不吃馅21 天前
揭秘 X6 核心概念:Graph、Node、Edge 与 View
前端·javascript·svg
吃饺子不吃馅21 天前
如何让AntV X6 的连线“动”起来:实现流动效果?
前端·css·svg
拜无忧1 个月前
html,svg,花海扩散效果
前端·css·svg