svgs缩放及工艺图的操作

142 阅读4分钟
     <!-- 工艺图 -->
          <div id="svgDiv" style="width: 100%; height: 100%">
            <embed
              id="svgView"
              name="svgView"
              src
              wmode="transparent"
              width="100%"
              height="100%"
              type="image/svg+xml"
            />
          </div>

/*
 * @Description:  封装成useSvgPanZoom 放大缩小及svg样式的初始化
 * 用法:传入svgDOM,调用initSvg方法即可
 * import  {useSvgPanZoom}  from '@/hooks/useSvgPanZoom'
 * const { initSvg,initStyle } = useSvgPanZoom(svgDom)
      initSvg()
      // 初始化工艺图样式
      initStyle(primaryColor.value, svgdoc.value)
 */

export const useSvgPanZoom = () => {
    let zoomVal = 0 // 放大缩小累计值
    let zoomStepSize = 20 // 放大缩小每次改变的步长20
    let removeFlag = false // 控制点击拖动,区分未点击时的鼠标移动
    let zoomW = 41 // 初始值 > zoomStepSize * 2 初次滚动后即可得到准确的当前值
    let zoomH = 41 // 初始值 > zoomStepSize * 2 初次滚动后即可得到准确的当前值
    let zoomX = 0
    let zoomY = 0
    let startX = 0
    let startY = 0
    let endX = 0
    let endY = 0
    let moveX = 0
    let moveY = 0
    let vbCX = 0
    let vbCY = 0
    let vbCW = 0
    let vbCH = 0
    let svgdoc = null
    let svgDom = null
    function initSvg(svg) {
        svgdoc = svg
        svgDom = svg.getElementsByTagName('svg')[0]
        svgDom.addEventListener('mousedown', moveDownMouse, { passive: false })
        svgDom.addEventListener('mouseup', moveUpMouse, { passive: false })
        svgDom.addEventListener('mousemove', moveMouse, { passive: false })
        svgDom.addEventListener('mousewheel', MouseWheel, { passive: false })
        getCurrentVB()
    }
    /**
 * @param {*} color 颜色
 * @return {*}
 * @description: 工艺图样式初始化
 */
    function initStyle(color) {
        // 标题更改样式
        let titleNode = svgdoc.querySelectorAll("[id^='Title']")
        titleNode.forEach((element) => {
            let node = element.firstChild || element
            node.style.setProperty('stroke', color, 'important')
            node.style.setProperty('fill', color, 'important')
        })
        // 文字更改样式
        let textNode = svgdoc.querySelectorAll("[id^='text']")
        textNode.forEach((element) => {
            let node = element.firstChild || element
            node.style.setProperty('stroke', color, 'important')
            node.style.setProperty('fill', color, 'important')
        })
        // 添加手
        let dNode = svgdoc.querySelectorAll("[id^='D']")
        dNode.forEach((element) => {
            let node = element
            node.style.setProperty('cursor', 'pointer', 'important')
        })
        let scNode = svgdoc.querySelectorAll("[id^='SC']")
        scNode.forEach((element) => {
            let node = element.firstChild || element
            node.style.setProperty('cursor', 'pointer', 'important')
        })
        let EcNode = svgdoc.querySelectorAll("[id^='E']")
        EcNode.forEach((element) => {
            let node = element.firstChild || element
            node.style.setProperty('cursor', 'pointer', 'important')
        })
        let VcNode = svgdoc.querySelectorAll("[id^='V']")
        VcNode.forEach((element) => {
            let node = element.firstChild || element
            node.style.setProperty('cursor', 'pointer', 'important')
        })

        let LNode = svgdoc.querySelectorAll("[id^='L']")
        LNode.forEach((element) => {
            let node = element.firstChild || element
            // 管道时 添加动画
            createItem(node, element.id)
            node.style.setProperty('cursor', 'pointer', 'important')
        })
    }
    /**
     * @param {*} node 元素dom
     * @param {*} id id
     * @return {*}
     * @description:  流动动画
     */
    function createItem(node, id) {
        if (id.indexOf('_animateMotionG') > -1) {
            return
        }
        let path
        let d = node.getAttribute('d')
        if (d) {
            path = d
        } else if (node.getElementsByTagName('polyline').length) {
            node = node.getElementsByTagName('polyline').item(0)
            if (node) {
                let pointStrs = node.getAttribute('points')
                let points = pointStrs.split(',')
                path = 'M ' + points[0] + ' ' + points[1]
                for (let i = 2; i < points.length; i++) {
                    //计算path
                    if (i % 2 != 0) {
                        path += ' ' + (points[i] - points[i - 2])
                    } else {
                        path += ' l' + (points[i] - points[i - 2])
                    }
                }
            }
        }
        if (path) {
            let gEle = svgdoc.getElementById(id + '_animateMotionG')
            if (!gEle) {
                gEle = svgdoc.createElementNS('http://www.w3.org/2000/svg', 'g')
                gEle.setAttribute('id', id + '_animateMotionG')
                let maxTime = 6
                try {
                    let length = node.getTotalLength()
                    if (length) {
                        maxTime = parseInt(length / 50)
                        if (maxTime < 2) {
                            maxTime = 2
                        } else if (maxTime > 10) {
                            maxTime = 10
                        }
                    }
                } catch (e) {
                    console.log('管线' + id + '获取长度有异常~' + e)
                }

                for (let index = 0; index < maxTime; index++) {
                    let animation = svgdoc.createElementNS('http://www.w3.org/2000/svg', 'animateMotion') //创建一个移动动画
                    animation.setAttribute('begin', index + 's')
                    animation.setAttribute('dur', maxTime + 's')
                    animation.setAttribute('rotate', 'auto')
                    animation.setAttribute('repeatCount', 'indefinite')
                    animation.setAttribute('path', path)

                    let polygon = svgdoc.createElementNS('http://www.w3.org/2000/svg', 'polygon')
                    let lineWidth = '0'
                    try {
                        lineWidth = node.getStyle().getPropertyValue('stroke-width')
                    } catch (e) {
                        lineWidth = node.style.strokeWidth.replace('px', '')
                    }
                    polygon.setAttribute(
                        'points',
                        (4.3 * lineWidth) / 10 +
                        ',0 -' +
                        (4.3 * lineWidth) / 10 +
                        ',-' +
                        (5 * lineWidth) / 10 +
                        ' -' +
                        (4.3 * lineWidth) / 10 +
                        ',' +
                        (5 * lineWidth) / 10
                    )
                    let lineColor = 'purple'
                    if (id && id.indexOf('LineAir') > -1) {
                        //LineAir LineWater LineSludge LineDrug
                        lineColor = '#004501'
                    } else if (id && id.indexOf('LineWater') > -1) {
                        //LineAir LineWater LineSludge LineDrug
                        lineColor = '#8eabff'
                    } else if (id && id.indexOf('LineSludge') > -1) {
                        //LineAir LineWater LineSludge LineDrug
                        lineColor = '#f78855'
                    } else if (id && id.indexOf('LineDrug') > -1) {
                        //LineAir LineWater LineSludge LineDrug
                        lineColor = '#676700'
                    }
                    polygon.setAttribute(
                        'style',
                        'fill:' + lineColor + ';stroke:' + lineColor + ';stroke-width:0.1'
                    )
                    polygon.appendChild(animation)
                    gEle.appendChild(polygon)
                }
                node.parentNode.appendChild(gEle)
            } else {
                //已存在 已是开启状态
            }
        }
    }
    // 鼠标滚轮事件
    function MouseWheel(e) {
        let ev = e || window.event
        let down = true

        // ev.wheelDelta未定义,ev.detail > 0为true表示往下滚动,为false表示往上滚动
        // wheelDelta和detail正好相反,火狐中wheelDelta未定义只能使用detail
        down = ev.wheelDelta ? ev.wheelDelta < 0 : ev.detail > 0
        if (down) {
            zoomOut() // 缩小
        } else {
            zoomIn() // 放大
        }
        if (ev.preventDefault) {
            ev.preventDefault()
        }
        return false
    }
    function zoomIn() {
        if (zoomW > zoomStepSize * 2 && zoomH > zoomStepSize * 2) {
            zoomVal += zoomStepSize
            zoomTo('in')
        }
    }
    function zoomOut() {
        zoomVal -= zoomStepSize
        if (zoomVal >= -zoomStepSize * 11) {
            zoomTo('out')
        } else {
            zoomVal = -zoomStepSize * 11 // 最小缩至-220不能再缩了
        }
    }
    function zoomTo(flag) {
        // 变化之前获取当前viewBox的位置
        getCurrentVB()
        // *********** 放大缩小后的各值的计算 开始 ***********
        if (flag === 'in') {
            zoomX = vbCX + zoomStepSize
            zoomY = vbCY + zoomStepSize
            zoomW = vbCW - zoomStepSize * 2
            zoomH = vbCH - zoomStepSize * 2
        } else {
            zoomX = vbCX - zoomStepSize
            zoomY = vbCY - zoomStepSize
            zoomW = vbCW + zoomStepSize * 2
            zoomH = vbCH + zoomStepSize * 2
        }
        // *********** 放大缩小后的各值的计算 结束 ***********
        // 将计算的结果赋予viewBox刷新当前展示的视图位置
        svgDom.setAttributeNS(null, 'viewBox', zoomX + ' ' + zoomY + ' ' + zoomW + ' ' + zoomH)
        // 变化之后获取当前viewBox的位置 保证了 vbCX vbCY vbCW vbCH始终为当前viewBox的值
        endZoom()
    }
    function endZoom() {
        getCurrentVB()
        // 视图发生系列缩放改变后最终的(x, y)坐标记录下来,用于放大后拖动
        endX = vbCX
        endY = vbCY
    }

    // 鼠标点击事件
    function moveDownMouse(evt) {
        getCurrentVB()
        removeFlag = true
        startX = parseInt(evt.clientX) // 当前点击的点的横坐标
        startY = parseInt(evt.clientY) // 当前点击的点的纵坐标
    }
    // 鼠标移动事件
    function moveMouse(evt) {
        if (removeFlag) {
            svgDom.setAttributeNS(null, 'style', 'cursor: move')
            moveX = parseInt(evt.clientX) - startX // 当前点-原始点=移动量
            moveY = parseInt(evt.clientY) - startY // 当前点-原始点=移动量
            vbCX = endX - moveX
            vbCY = endY - moveY
            vbCW = parseFloat(svgDom.viewBox.animVal.width) //刷新获取viewBox的高和宽
            vbCH = parseFloat(svgDom.viewBox.animVal.height) //刷新获取viewBox的高和宽
            // 刷新当前viewBox展示的视图位置
            svgDom.setAttributeNS(null, 'viewBox', vbCX + ' ' + vbCY + ' ' + vbCW + ' ' + vbCH)
        }
    }
    // 鼠标点击后松开事件
    function moveUpMouse(evt) {
        svgDom.setAttributeNS(null, 'style', 'cursor: default')
        // 视图发生系列移动后最终的(x, y)坐标记录下来,用于放大后拖动
        endX = vbCX
        endY = vbCY
        removeFlag = false
    }

    // 获取当前视图VB的位置信息
    function getCurrentVB() {
        vbCX = parseFloat(svgDom.viewBox.animVal.x)
        vbCY = parseFloat(svgDom.viewBox.animVal.y)
        vbCW = parseFloat(svgDom.viewBox.animVal.width)
        vbCH = parseFloat(svgDom.viewBox.animVal.height)
    }
    // 绑定事件到dom节点,考虑到兼容性
    function addEvent(obj, xEvent, fn) {
        if (obj.attachEvent) {
            obj.attachEvent('on' + xEvent, fn)
        } else {
            obj.addEventListener(xEvent, fn, false)
        }
    }
    return {
        addEvent,
        getCurrentVB,
        moveUpMouse,
        moveMouse,
        moveDownMouse,
        endZoom,
        zoomTo,
        zoomIn,
        MouseWheel,
        initSvg,
        initStyle,
        createItem
    }
}
 <!-- 工艺图 -->
      <div id="svgDiv" style="width: 100%; height: 100%">
        <embed
          id="svgView"
          name="svgView"
          src
          wmode="transparent"
          width="100%"
          height="100%"
          type="image/svg+xml"
        />
      </div>
//svg的使用
let svgDom = null
const init = (info) => {
  //  proDrawUrl 工艺图svg ,必须是符合同源策略
  let {  proDrawUrl } = info
  if (!proDrawUrl) return
  nextTick(() => {
    let svgView = ''
    if (isIE()) {
      let svgDiv = document.getElementById('svgDiv')
      let _svgView = document.getElementById('svgView')
      if (_svgView) {
        svgDiv.removeChild(_svgView)
      }
      svgView = document.createElement('embed')
      svgView.setAttribute('id', 'svgView')
      svgView.setAttribute('name', 'svgView')
      svgView.setAttribute('src', proDrawUrl)
      svgView.setAttribute('wmode', 'transparent')
      svgView.setAttribute('width', '100%')
      svgView.setAttribute('height', document.body.clientHeight - 70 + 'px')
      svgView.setAttribute('type', 'image/svg+xml')
      svgDiv.appendChild(svgView)
    } else {
      svgView = document.getElementById('svgView')
      svgView.src = proDrawUrl
    }
    svgView.onload = function () {
      svgdoc.value = null
      svgdoc.value = svgView.getSVGDocument()
      if (!svgdoc.value) {
        svgdoc.value = svgView.contentDocument
      }
      const { initSvg, initStyle } = useSvgPanZoom()
      initSvg(svgdoc.value)
      // 初始化工艺图样式
      initStyle(primaryColor.value)
      //添加svg的点击事情
      svgdoc.value.addEventListener('click', (e) => {
        // L 管线(流动动画)  E 开关(背景颜色)  D 数据配置(数据显示)  A  鼓风机(旋转动画)
        if (
          e.target.id.indexOf('E') !== -1 ||
          e.target.id.indexOf('L') !== -1 ||
          e.target.id.indexOf('D') !== -1
        ) {
          showDrawer(e.target.id, id, factoryId)
        }

      })
    }
  })
}