彻底理解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图片。

相关推荐
德育处主任16 天前
前端啊,拿Lottie炫个动画吧
前端·svg·canvas
eggcode21 天前
C#生成SVG文件(文本、线段、圆、椭圆、多边形的示例)
c#·svg
巽星石1 个月前
在VSCode中使用Excalidraw
vscode·svg·excalidraw
起来改bug2 个月前
svg画进度条
css·进度条·svg
孙中毅3 个月前
SVG 路径动画的实现原理
css·svg·动效
白雾茫茫丶3 个月前
Nest.js 实战 (七):如何生成 SVG 图形验证码
svg·nest.js
xachary3 个月前
手把手使用 SVG + CSS 实现渐变进度环效果
前端·css3·svg
xachary3 个月前
CSS mask-image 实现边缘淡出过渡效果
前端·css·css3·svg
leopai3 个月前
最受欢迎SVG图标库揭秘!为 React 应用注入矢量动力!
前端·react.js·svg
大漠_w3cpluscom4 个月前
聊聊 Web 中的圆
前端·css·svg