panzoom.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. <template>
  2. <g>
  3. <rect
  4. class="flow-pan-zoom__grid"
  5. width="100%"
  6. height="100%"
  7. fill="url(#grid)"
  8. />
  9. <rect
  10. ref="transformer"
  11. class="flow-pan-zoom__transformer"
  12. width="100%"
  13. height="100%"
  14. @mousedown="dragStart"/>
  15. <g
  16. class="flow-pan-zoom__transformed"
  17. ref="transform"
  18. v-bind="transformProps">
  19. <slot/>
  20. </g>
  21. </g>
  22. </template>
  23. <script>
  24. import utils from '@/utils/utils'
  25. export default {
  26. name: 'FlowPanZoom',
  27. props: {
  28. value: {type: Object, default: () => { return {x: 0, y: 0, zoom: 1} }}
  29. },
  30. data () {
  31. return {
  32. zoom: this.value.zoom,
  33. x: this.value.x,
  34. y: this.value.y,
  35. moving: false
  36. }
  37. },
  38. computed: {
  39. transformProps () {
  40. const transString = 'matrix(' + [
  41. this.zoom, 0, 0,
  42. this.zoom, this.x, this.y
  43. ].join(' ') + ')'
  44. return {
  45. transform: transString,
  46. class: 'flow-pan-zoom__transform ' + this.moving ? 'moving' : ''
  47. }
  48. }
  49. },
  50. watch: {
  51. value: {
  52. handler () {
  53. this.zoom = this.value.zoom
  54. this.x = this.value.x
  55. this.y = this.value.y
  56. },
  57. deep: true
  58. }
  59. },
  60. mounted () {
  61. this.$el.addEventListener('wheel', this.wheel)
  62. },
  63. beforeDestroy () {
  64. this.$el.removeEventListener('wheel', this.wheel)
  65. },
  66. methods: {
  67. // panStart
  68. dragStart (ev) {
  69. document.activeElement && document.activeElement.blur()
  70. if (!(ev.button === 1 || (ev.button === 0 && ev.ctrlKey))) return // first button
  71. if (ev.target !== this.$refs.transformer) return
  72. ev.stopPropagation()
  73. utils.createDrag({
  74. drag: (ev) => {
  75. this.moving = true
  76. this.update(this.x + ev.movementX, this.y + ev.movementY)
  77. },
  78. drop: (ev) => {
  79. this.moving = false
  80. }
  81. })
  82. },
  83. wheel (ev) {
  84. ev.preventDefault()
  85. let deltaY = (ev.deltaY > 0) ? 1 : -1
  86. deltaY *= (ev.shiftKey) ? 0.3 : 0.07
  87. const svgRect = this.$refs.transformer.getBoundingClientRect()
  88. const oX = ev.clientX - svgRect.left
  89. const oY = ev.clientY - svgRect.top
  90. const z = Math.max(this.zoom - (deltaY * this.zoom), 0.1)
  91. var curX = this.x
  92. var curY = this.y
  93. var scaleD = z / this.zoom // delta
  94. var nx = scaleD * (curX - oX) + oX
  95. var ny = scaleD * (curY - oY) + oY
  96. this.update(nx, ny, z)
  97. },
  98. update (x, y, zoom) {
  99. if (x) this.x = x
  100. if (y) this.y = y
  101. if (zoom) this.zoom = zoom
  102. this.$emit('input', {x: this.x, y: this.y, zoom: this.zoom})
  103. },
  104. transformedPoint (point) {
  105. const ctm = this.$refs.transform.getCTM()
  106. return point.matrixTransform(ctm.inverse())
  107. }
  108. }
  109. }
  110. </script>
  111. <style>
  112. .flow-pan-zoom__transformer {
  113. fill:transparent;
  114. }
  115. .flow-pan-zoom__transformed {
  116. transition: transform 0.15s ease;
  117. }
  118. .flow-pan-zoom__grid {
  119. fill:transparent;
  120. pointer-events:none;
  121. }
  122. </style>