hotzone.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. let _ = {
  2. MIN_LIMIT: 48, // Min size of zone
  3. DECIMAL_PLACES: 4 // Hotzone positioning decimal point limit number of digits
  4. }
  5. /**
  6. * Get a power result of 10 for the power of the constant
  7. * @return {Number}
  8. */
  9. _.getMultiple = (decimalPlaces = _.DECIMAL_PLACES) => {
  10. return Math.pow(10, decimalPlaces)
  11. }
  12. /**
  13. * Limit decimal places
  14. * @param {Number} num
  15. * @return {Number}
  16. */
  17. _.decimalPoint = (val = 0) => {
  18. return Math.round(val * _.getMultiple()) / _.getMultiple() || 0
  19. }
  20. /**
  21. * Get element width and height
  22. * @param {Object} elem
  23. * @return {Object}
  24. */
  25. _.getOffset = (elem = {}) => ({
  26. width: elem.clientWidth || 0,
  27. height: elem.clientHeight || 0
  28. })
  29. /**
  30. * Get pageX
  31. * @param {Object} e
  32. * @return {Number}
  33. */
  34. _.getPageX = (e) => ('pageX' in e) ? e.pageX : e.touches[0].pageX
  35. /**
  36. * Get pageY
  37. * @param {Object} e
  38. * @return {Number}
  39. */
  40. _.getPageY = (e) => ('pageY' in e) ? e.pageY : e.touches[0].pageY
  41. /**
  42. * Gets the abscissa value of the mouse click relative to the target node
  43. * @param {Object} e
  44. * @param {Object} container
  45. * @return {Number}
  46. */
  47. _.getDistanceX = (e, container) =>
  48. _.getPageX(e) - (container.getBoundingClientRect().left + window.pageXOffset)
  49. /**
  50. * Gets the ordinate value of the mouse click relative to the target node
  51. * @param {Object} e
  52. * @param {Object} container
  53. * @return {Number}
  54. */
  55. _.getDistanceY = (e, container) =>
  56. _.getPageY(e) - (container.getBoundingClientRect().top + window.pageYOffset)
  57. /**
  58. * Treatment of boundary conditions when changing the size of the hotzone
  59. * @param {Object} itemInfo
  60. * @param {Object} styleInfo
  61. * @param {Object} container
  62. */
  63. _.dealEdgeValue = (itemInfo, styleInfo, container) => {
  64. if (styleInfo.hasOwnProperty('left') && styleInfo.left < 0) {
  65. styleInfo.left = 0
  66. styleInfo.width = itemInfo.width + itemInfo.left
  67. }
  68. if (styleInfo.hasOwnProperty('top') && styleInfo.top < 0) {
  69. styleInfo.top = 0
  70. styleInfo.height = itemInfo.height + itemInfo.top
  71. }
  72. if (!styleInfo.hasOwnProperty('left') && styleInfo.hasOwnProperty('width')) {
  73. if (itemInfo.left + styleInfo.width > container.width) {
  74. styleInfo.width = container.width - itemInfo.left
  75. }
  76. }
  77. if (!styleInfo.hasOwnProperty('top') && styleInfo.hasOwnProperty('height')) {
  78. if (itemInfo.top + styleInfo.height > container.height) {
  79. styleInfo.height = container.height - itemInfo.top
  80. }
  81. }
  82. return Object.assign(itemInfo, styleInfo)
  83. }
  84. /**
  85. * Handle different drag points, capital letters mean: T-top,L-left,C-center,R-right,B-bottom
  86. * @param {Object} itemInfo
  87. * @param {Number} moveX
  88. * @param {Number} moveY
  89. * @return {Object}
  90. */
  91. _.dealTL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  92. let styleInfo = {}
  93. let width = itemInfo.width - moveX
  94. let height = itemInfo.height - moveY
  95. if (width >= Math.min(minLimit, itemInfo.width)) {
  96. Object.assign(styleInfo, {
  97. width,
  98. left: itemInfo.left + moveX
  99. })
  100. }
  101. if (height >= Math.min(minLimit, itemInfo.height)) {
  102. Object.assign(styleInfo, {
  103. height,
  104. top: itemInfo.top + moveY
  105. })
  106. }
  107. return styleInfo
  108. }
  109. _.dealTC = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  110. let styleInfo = {}
  111. let height = itemInfo.height - moveY
  112. if (height >= Math.min(minLimit, itemInfo.height)) {
  113. styleInfo = {
  114. height,
  115. top: itemInfo.top + moveY
  116. }
  117. }
  118. return styleInfo
  119. }
  120. _.dealTR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  121. let styleInfo = {}
  122. let width = itemInfo.width + moveX
  123. let height = itemInfo.height - moveY
  124. if (width >= Math.min(minLimit, itemInfo.width)) {
  125. Object.assign(styleInfo, {
  126. width
  127. })
  128. }
  129. if (height >= Math.min(minLimit, itemInfo.height)) {
  130. Object.assign(styleInfo, {
  131. height,
  132. top: itemInfo.top + moveY
  133. })
  134. }
  135. return styleInfo
  136. }
  137. _.dealCL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  138. let styleInfo = {}
  139. let width = itemInfo.width - moveX
  140. if (width >= Math.min(minLimit, itemInfo.width)) {
  141. Object.assign(styleInfo, {
  142. width,
  143. left: itemInfo.left + moveX
  144. })
  145. }
  146. return styleInfo
  147. }
  148. _.dealCR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  149. let styleInfo = {}
  150. let width = itemInfo.width + moveX
  151. if (width >= Math.min(minLimit, itemInfo.width)) {
  152. Object.assign(styleInfo, {
  153. width
  154. })
  155. }
  156. return styleInfo
  157. }
  158. _.dealBL = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  159. let styleInfo = {}
  160. let width = itemInfo.width - moveX
  161. let height = itemInfo.height + moveY
  162. if (width >= Math.min(minLimit, itemInfo.width)) {
  163. Object.assign(styleInfo, {
  164. width,
  165. left: itemInfo.left + moveX
  166. })
  167. }
  168. if (height >= Math.min(minLimit, itemInfo.height)) {
  169. Object.assign(styleInfo, {
  170. height
  171. })
  172. }
  173. return styleInfo
  174. }
  175. _.dealBC = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  176. let styleInfo = {}
  177. let height = itemInfo.height + moveY
  178. if (height >= Math.min(minLimit, itemInfo.height)) {
  179. Object.assign(styleInfo, {
  180. height
  181. })
  182. }
  183. return styleInfo
  184. }
  185. _.dealBR = (itemInfo, moveX, moveY, minLimit = _.MIN_LIMIT) => {
  186. let styleInfo = {}
  187. let width = itemInfo.width + moveX
  188. let height = itemInfo.height + moveY
  189. if (width >= Math.min(minLimit, itemInfo.width)) {
  190. Object.assign(styleInfo, {
  191. width
  192. })
  193. }
  194. if (height >= Math.min(minLimit, itemInfo.height)) {
  195. Object.assign(styleInfo, {
  196. height
  197. })
  198. }
  199. return styleInfo
  200. }
  201. let addItem = {
  202. bind: function (el, binding, vnode) {
  203. const MIN_LIMIT = _.MIN_LIMIT
  204. // 拖拽添加
  205. // el.addEventListener('mousedown', handleMouseDown)
  206. function handleMouseDown (e) {
  207. e && e.preventDefault()
  208. let itemInfo = {
  209. top: _.getDistanceY(e, el),
  210. left: _.getDistanceX(e, el),
  211. width: 0,
  212. height: 0
  213. }
  214. let container = _.getOffset(el)
  215. // Only used once at the beginning of init
  216. let setting = {
  217. topPer: _.decimalPoint(itemInfo.top / container.height),
  218. leftPer: _.decimalPoint(itemInfo.left / container.width),
  219. widthPer: 0,
  220. heightPer: 0
  221. }
  222. let preX = _.getPageX(e)
  223. let preY = _.getPageY(e)
  224. vnode.context.addhotZoneItem(setting)
  225. window.addEventListener('mousemove', handleChange)
  226. window.addEventListener('mouseup', handleMouseUp)
  227. function handleChange (e) {
  228. e && e.preventDefault()
  229. let moveX = _.getPageX(e) - preX
  230. let moveY = _.getPageY(e) - preY
  231. preX = _.getPageX(e)
  232. preY = _.getPageY(e)
  233. // Not consider the direction of movement first, consider only the lower right drag point
  234. let minLimit = 0
  235. let styleInfo = _.dealBR(itemInfo, moveX, moveY, minLimit)
  236. // Boundary value processing
  237. itemInfo = _.dealEdgeValue(itemInfo, styleInfo, container)
  238. Object.assign(el.lastElementChild.style, {
  239. top: `${itemInfo.top}px`,
  240. left: `${itemInfo.left}px`,
  241. width: `${itemInfo.width}px`,
  242. height: `${itemInfo.height}px`
  243. })
  244. }
  245. function handleMouseUp () {
  246. let perInfo = {
  247. topPer: _.decimalPoint(itemInfo.top / container.height),
  248. leftPer: _.decimalPoint(itemInfo.left / container.width),
  249. widthPer: _.decimalPoint(itemInfo.width / container.width),
  250. heightPer: _.decimalPoint(itemInfo.height / container.height)
  251. }
  252. if (vnode.context.isOverRange()) {
  253. vnode.context.overRange()
  254. } else if (container.height < MIN_LIMIT && itemInfo.width > MIN_LIMIT) {
  255. vnode.context.changeItem(Object.assign(perInfo, {
  256. topPer: 0,
  257. heightPer: 1
  258. }))
  259. } else if (container.width < MIN_LIMIT && itemInfo.height > MIN_LIMIT) {
  260. vnode.context.changeItem(Object.assign(perInfo, {
  261. leftper: 0,
  262. widthPer: 1
  263. }))
  264. } else if (itemInfo.width > MIN_LIMIT && itemInfo.height > MIN_LIMIT) {
  265. vnode.context.changeItem(perInfo)
  266. } else {
  267. vnode.context.eraseItem()
  268. }
  269. window.removeEventListener('mousemove', handleChange)
  270. window.removeEventListener('mouseup', handleMouseUp)
  271. }
  272. }
  273. el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
  274. },
  275. unbind: function (el) {
  276. el.$destroy()
  277. }
  278. }
  279. let changeSize = {
  280. bind: function (el, binding, vnode) {
  281. el.addEventListener('mousedown', handleMouseDown)
  282. function handleMouseDown (e) {
  283. let pointer = e.target.dataset.pointer
  284. if (!pointer) {
  285. return
  286. }
  287. e && e.stopPropagation()
  288. let zone = el.parentNode
  289. let setting = vnode.context.setting
  290. let container = _.getOffset(zone.parentNode)
  291. let itemInfo = {
  292. width: _.getOffset(zone).width || 0,
  293. height: _.getOffset(zone).height || 0,
  294. top: setting.topPer * container.height || 0,
  295. left: setting.leftPer * container.width || 0
  296. }
  297. let preX = _.getPageX(e)
  298. let preY = _.getPageY(e)
  299. let flag
  300. // Hide the info displayed by hover
  301. vnode.context.handlehideZone(true)
  302. window.addEventListener('mousemove', handleChange)
  303. window.addEventListener('mouseup', handleMouseUp)
  304. function handleChange (e) {
  305. e && e.preventDefault()
  306. flag = true
  307. let moveX = _.getPageX(e) - preX
  308. let moveY = _.getPageY(e) - preY
  309. preX = _.getPageX(e)
  310. preY = _.getPageY(e)
  311. // Handling the situation when different dragging points are selected
  312. let styleInfo = _[pointer](itemInfo, moveX, moveY)
  313. // Boundary value processing
  314. itemInfo = _.dealEdgeValue(itemInfo, styleInfo, container)
  315. Object.assign(zone.style, {
  316. top: `${itemInfo.top}px`,
  317. left: `${itemInfo.left}px`,
  318. width: `${itemInfo.width}px`,
  319. height: `${itemInfo.height}px`
  320. })
  321. }
  322. function handleMouseUp () {
  323. if (flag) {
  324. flag = false
  325. let perInfo = {
  326. topPer: _.decimalPoint(itemInfo.top / container.height),
  327. leftPer: _.decimalPoint(itemInfo.left / container.width),
  328. widthPer: _.decimalPoint(itemInfo.width / container.width),
  329. heightPer: _.decimalPoint(itemInfo.height / container.height)
  330. }
  331. vnode.context.changeInfo(perInfo)
  332. // 兼容数据无变更情况下导致 computed 不更新,数据仍为 px 时 resize 出现的问题
  333. Object.assign(zone.style, {
  334. top: `${itemInfo.top}px`,
  335. left: `${itemInfo.left}px`,
  336. width: `${itemInfo.width}px`,
  337. height: `${itemInfo.height}px`
  338. })
  339. }
  340. // Show the info
  341. vnode.context.handlehideZone(false)
  342. window.removeEventListener('mousemove', handleChange)
  343. window.removeEventListener('mouseup', handleMouseUp)
  344. }
  345. }
  346. el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
  347. },
  348. unbind: function (el) {
  349. el.$destroy()
  350. }
  351. }
  352. let dragItem = {
  353. bind: function (el, binding, vnode) {
  354. el.addEventListener('mousedown', handleMouseDown)
  355. function handleMouseDown (e) {
  356. e && e.stopPropagation()
  357. let container = _.getOffset(el.parentNode)
  358. let preX = _.getPageX(e)
  359. let preY = _.getPageY(e)
  360. let topPer
  361. let leftPer
  362. let flag
  363. window.addEventListener('mousemove', handleChange)
  364. window.addEventListener('mouseup', handleMouseUp)
  365. function handleChange (e) {
  366. e && e.preventDefault()
  367. flag = true
  368. // Hide the info displayed by hover
  369. vnode.context.handlehideZone(true)
  370. let setting = vnode.context.setting
  371. let moveX = _.getPageX(e) - preX
  372. let moveY = _.getPageY(e) - preY
  373. setting.topPer = setting.topPer || 0
  374. setting.leftPer = setting.leftPer || 0
  375. topPer = _.decimalPoint(moveY / container.height + setting.topPer)
  376. leftPer = _.decimalPoint(moveX / container.width + setting.leftPer)
  377. // Hotzone moving boundary processing
  378. if (topPer < 0) {
  379. topPer = 0
  380. moveY = -container.height * setting.topPer
  381. }
  382. if (leftPer < 0) {
  383. leftPer = 0
  384. moveX = -container.width * setting.leftPer
  385. }
  386. if (topPer + setting.heightPer > 1) {
  387. topPer = 1 - setting.heightPer
  388. moveY = container.height * (topPer - setting.topPer)
  389. }
  390. if (leftPer + setting.widthPer > 1) {
  391. leftPer = 1 - setting.widthPer
  392. moveX = container.width * (leftPer - setting.leftPer)
  393. }
  394. el.style.transform = `translate(${moveX}px, ${moveY}px)`
  395. }
  396. function handleMouseUp () {
  397. if (flag) {
  398. flag = false
  399. el.style.transform = 'translate(0, 0)'
  400. vnode.context.changeInfo({
  401. topPer,
  402. leftPer
  403. })
  404. }
  405. // Show the info
  406. vnode.context.handlehideZone(false)
  407. window.removeEventListener('mousemove', handleChange)
  408. window.removeEventListener('mouseup', handleMouseUp)
  409. }
  410. }
  411. el.$destroy = () => el.removeEventListener('mousedown', handleMouseDown)
  412. },
  413. unbind: function (el) {
  414. el.$destroy()
  415. }
  416. }
  417. // export default _