Selection.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import { throttle, getElementCorners, getBoundingRect } from './utils'
  2. import Rectangle from './elements/Rectangle'
  3. import Canvas from './Canvas'
  4. import Coordinate from './Coordinate'
  5. import MultiSelectElement from './elements/MultiSelectElement'
  6. import { DRAG_ELEMENT_PARTS } from './constants'
  7. // 多选类
  8. export default class Selection {
  9. constructor(app) {
  10. this.app = app
  11. this.canvas = null
  12. this.ctx = null
  13. // 当前是否正在创建选区中
  14. this.creatingSelection = false
  15. // 当前是否存在多选元素
  16. this.hasSelection = false
  17. // 当前是否正在调整被选中的元素
  18. this.isResizing = false
  19. this.state = this.app.state
  20. this.width = this.app.width
  21. this.height = this.app.height
  22. this.coordinate = new Coordinate(this)
  23. // 选区矩形
  24. this.rectangle = new Rectangle(
  25. {
  26. type: 'rectangle',
  27. style: {
  28. strokeStyle: 'rgba(9,132,227,0.3)',
  29. fillStyle: 'rgba(9,132,227,0.3)'
  30. }
  31. },
  32. this
  33. )
  34. // 被选中的元素的虚拟元素,用于显示拖拽框
  35. this.multiSelectElement = new MultiSelectElement(
  36. {
  37. type: 'multiSelectElement'
  38. },
  39. this
  40. )
  41. this.checkInNodes = throttle(this.checkInNodes, this, 500)
  42. // 稍微缓解一下卡顿
  43. this.handleResize = throttle(this.handleResize, this, 16)
  44. this.init()
  45. this.bindEvent()
  46. }
  47. // 初始化
  48. init() {
  49. if (this.canvas) {
  50. this.app.container.removeChild(this.canvas.el)
  51. }
  52. this.width = this.app.width
  53. this.height = this.app.height
  54. // 创建canvas元素
  55. this.canvas = new Canvas(this.width, this.height, {
  56. className: 'selection'
  57. })
  58. this.ctx = this.canvas.ctx
  59. this.app.container.appendChild(this.canvas.el)
  60. }
  61. // 监听事件
  62. bindEvent() {
  63. this.app.on('change', () => {
  64. this.state = this.app.state
  65. this.multiSelectElement.updateElements(this.app.elements.elementList)
  66. this.renderSelection()
  67. })
  68. this.app.on('scrollChange', () => {
  69. this.renderSelection()
  70. })
  71. this.app.on('zoomChange', () => {
  72. this.renderSelection()
  73. })
  74. }
  75. // 鼠标按下
  76. onMousedown(e, event) {
  77. if (e.originEvent.which !== 1) {
  78. return
  79. }
  80. this.creatingSelection = true
  81. this.rectangle.updatePos(event.mousedownPos.x, event.mousedownPos.y)
  82. }
  83. // 鼠标移动
  84. onMousemove(e, event) {
  85. if (
  86. Math.abs(event.mouseOffset.x) <= 10 &&
  87. Math.abs(event.mouseOffset.y) <= 10
  88. ) {
  89. return
  90. }
  91. this.onMove(e, event)
  92. }
  93. // 鼠标松开
  94. onMouseup() {
  95. this.creatingSelection = false
  96. this.rectangle.updateRect(0, 0, 0, 0)
  97. // 判断是否有元素被选中
  98. this.hasSelection = this.hasSelectionElements()
  99. this.multiSelectElement.updateRect()
  100. this.renderSelection()
  101. this.emitChange()
  102. }
  103. // 复位
  104. reset() {
  105. this.setMultiSelectElements([])
  106. this.hasSelection = false
  107. this.renderSelection()
  108. this.emitChange()
  109. }
  110. // 渲染
  111. renderSelection() {
  112. this.canvas.clearCanvas()
  113. this.ctx.save()
  114. this.ctx.scale(this.app.state.scale, this.app.state.scale)
  115. this.rectangle.render()
  116. this.multiSelectElement.render()
  117. this.ctx.restore()
  118. }
  119. // 鼠标移动事件
  120. onMove(e, event) {
  121. this.rectangle.updateSize(event.mouseOffset.x, event.mouseOffset.y)
  122. this.renderSelection()
  123. this.checkInElements(e, event)
  124. }
  125. // 检测在选区里的节点
  126. checkInElements(e, event) {
  127. let minx = Math.min(event.mousedownPos.x, e.clientX)
  128. let miny = Math.min(event.mousedownPos.y, e.clientY)
  129. let maxx = Math.max(event.mousedownPos.x, e.clientX)
  130. let maxy = Math.max(event.mousedownPos.y, e.clientY)
  131. let selectedElementList = []
  132. this.app.elements.elementList.forEach(element => {
  133. let _minx = Infinity
  134. let _maxx = -Infinity
  135. let _miny = Infinity
  136. let _maxy = -Infinity
  137. let endPointList = element.getEndpointList()
  138. let rect = getBoundingRect(
  139. endPointList.map(point => {
  140. return [point.x, point.y]
  141. }),
  142. true
  143. )
  144. rect.forEach(({ x, y }) => {
  145. if (x < _minx) {
  146. _minx = x
  147. }
  148. if (x > _maxx) {
  149. _maxx = x
  150. }
  151. if (y < _miny) {
  152. _miny = y
  153. }
  154. if (y > _maxy) {
  155. _maxy = y
  156. }
  157. })
  158. if (_minx >= minx && _maxx <= maxx && _miny >= miny && _maxy <= maxy) {
  159. selectedElementList.push(element)
  160. }
  161. })
  162. let finalList = [...selectedElementList]
  163. selectedElementList.forEach(item => {
  164. if (item.hasGroup()) {
  165. finalList.push(...this.app.group.getGroupElements(item))
  166. }
  167. })
  168. finalList = new Set(finalList)
  169. finalList = Array.from(finalList)
  170. this.setMultiSelectElements(finalList, true)
  171. this.app.render.render()
  172. }
  173. // 检测指定位置是否在元素调整手柄上
  174. checkInResizeHand(x, y) {
  175. return this.multiSelectElement.dragElement.checkPointInDragElementWhere(
  176. x,
  177. y
  178. )
  179. }
  180. // 检查是否需要进行元素调整操作
  181. checkIsResize(x, y, e) {
  182. if (!this.hasSelection) {
  183. return false
  184. }
  185. let hand = this.multiSelectElement.dragElement.checkPointInDragElementWhere(
  186. x,
  187. y
  188. )
  189. if (hand) {
  190. this.isResizing = true
  191. this.multiSelectElement.startResize(hand, e)
  192. this.app.cursor.setResize(hand)
  193. return true
  194. }
  195. return false
  196. }
  197. // 进行元素调整操作
  198. handleResize(...args) {
  199. if (!this.isResizing) {
  200. return
  201. }
  202. this.multiSelectElement.resize(...args)
  203. this.app.render.render()
  204. this.multiSelectElement.updateRect()
  205. this.renderSelection()
  206. }
  207. // 结束元素调整操作
  208. endResize() {
  209. this.isResizing = false
  210. this.multiSelectElement.endResize()
  211. }
  212. // 为多选元素设置样式
  213. setSelectedElementStyle(style = {}) {
  214. if (!this.hasSelectionElements()) {
  215. return
  216. }
  217. Object.keys(style).forEach(key => {
  218. this.getSelectionElements().forEach(element => {
  219. element.style[key] = style[key]
  220. if (key === 'fontSize' && element.type === 'text') {
  221. element.updateTextSize()
  222. this.multiSelectElement.updateRect()
  223. }
  224. })
  225. })
  226. this.app.render.render()
  227. this.app.emitChange()
  228. }
  229. // 删除当前选中的元素
  230. deleteSelectedElements() {
  231. this.getSelectionElements().forEach(element => {
  232. this.app.elements.deleteElement(element)
  233. })
  234. this.selectElements([])
  235. this.app.emitChange()
  236. }
  237. // 当前是否存在被选中元素
  238. hasSelectionElements() {
  239. return this.getSelectionElements().length > 0
  240. }
  241. // 获取当前被选中的元素
  242. getSelectionElements() {
  243. return this.multiSelectElement.selectedElementList
  244. }
  245. // 复制当前选中的元素
  246. async copySelectionElements(pos) {
  247. let task = this.getSelectionElements().map(element => {
  248. return this.app.elements.copyElement(element, true)
  249. })
  250. this.app.group.clearCopyMap()
  251. let elements = await Promise.all(task)
  252. this.setMultiSelectElements(elements)
  253. // 粘贴到指定位置
  254. if (pos) {
  255. this.multiSelectElement.startResize(DRAG_ELEMENT_PARTS.BODY)
  256. let ox =
  257. pos.x - this.multiSelectElement.x - this.multiSelectElement.width / 2
  258. let oy =
  259. pos.y - this.multiSelectElement.y - this.multiSelectElement.height / 2
  260. // 如果开启了网格,那么要坐标要吸附到网格
  261. let gridAdsorbentPos = this.app.coordinate.gridAdsorbent(ox, oy)
  262. this.multiSelectElement.resize(
  263. null,
  264. null,
  265. null,
  266. gridAdsorbentPos.x,
  267. gridAdsorbentPos.y
  268. )
  269. this.multiSelectElement.endResize()
  270. this.multiSelectElement.updateRect()
  271. }
  272. this.app.render.render()
  273. this.renderSelection()
  274. this.app.emitChange()
  275. }
  276. // 选中指定元素
  277. selectElements(elements = []) {
  278. this.hasSelection = elements.length > 0
  279. this.setMultiSelectElements(elements)
  280. this.app.render.render()
  281. this.renderSelection()
  282. this.emitChange()
  283. }
  284. // 设置选中的元素
  285. setMultiSelectElements(elements = [], notUpdateRect) {
  286. this.multiSelectElement.setSelectedElementList(elements)
  287. if (!notUpdateRect) {
  288. this.multiSelectElement.updateRect()
  289. }
  290. }
  291. // 触发多选元素变化事件
  292. emitChange() {
  293. this.app.emit('multiSelectChange', this.getSelectionElements())
  294. }
  295. }