AreaCropper.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <template>
  2. <div class="area-cropper">
  3. <div class="cropper-img">
  4. <img
  5. v-if="paper.imgUrl"
  6. ref="imgDom"
  7. :src="paper.imgUrl"
  8. @load="imgLoad"
  9. />
  10. </div>
  11. <div
  12. class="cropper-areas"
  13. v-move-ele.prevent="{
  14. moveStart: ($event) => {
  15. boxMouseDown($event);
  16. },
  17. moveElement: (pos, $event) => {
  18. boxMove($event);
  19. },
  20. moveStop: () => {
  21. boxMoveStop();
  22. },
  23. }"
  24. >
  25. <area-item
  26. v-for="area in areas"
  27. :key="area.key"
  28. :data="area"
  29. :cur-element="curArea"
  30. @del-element="removeArea"
  31. @act-element="actCurArea"
  32. @resize-over="modifyArea"
  33. ></area-item>
  34. <!-- 拖动选框 -->
  35. <div
  36. v-if="selectionStyles.w"
  37. class="area-selection"
  38. :style="{
  39. width: selectionStyles.w + 'px',
  40. height: selectionStyles.h + 'px',
  41. top: selectionStyles.y + 'px',
  42. left: selectionStyles.x + 'px',
  43. }"
  44. ></div>
  45. </div>
  46. </div>
  47. </template>
  48. <script>
  49. import MoveEle from "./move-ele";
  50. import AreaItem from "./ElementItem.vue";
  51. import { randomCode } from "@/plugins/utils";
  52. export default {
  53. name: "area-cropper",
  54. components: { AreaItem },
  55. directives: { MoveEle },
  56. props: {
  57. paper: {
  58. type: Object,
  59. default() {
  60. return {
  61. imgUrl: "",
  62. areas: [],
  63. };
  64. },
  65. },
  66. },
  67. data() {
  68. return {
  69. areas: [],
  70. initArea: { id: null, i: 0, x: 0, y: 0, w: 0, h: 0, zIndex: 0 },
  71. curArea: {},
  72. selectionStartPos: { x: 0, y: 0 },
  73. selectionStyles: { x: 0, y: 0, w: 0, h: 0 },
  74. IS_SELECT_ACTION: false,
  75. imgDisplayWidth: 0,
  76. imgDisplayHeight: 0,
  77. };
  78. },
  79. mounted() {
  80. window.addEventListener("resize", this.windowResizeEvent);
  81. document.addEventListener("keydown", this.keyEvent);
  82. },
  83. methods: {
  84. getMax(arr) {
  85. return Math.max.apply(null, arr);
  86. },
  87. toFixed(num, precision = 2) {
  88. return num.toFixed(precision) * 1;
  89. },
  90. imgLoad() {
  91. this.transformPicConfig(this.paper.areas);
  92. this.$emit("paper-load");
  93. },
  94. windowResizeEvent() {
  95. const { clientWidth, clientHeight } = this.$refs.imgDom;
  96. const hRate = clientHeight / this.imgDisplayHeight;
  97. const wRate = clientWidth / this.imgDisplayWidth;
  98. this.areas = this.areas.map((area) => {
  99. return Object.assign({}, area, {
  100. key: `key-${randomCode()}`,
  101. x: area.x * wRate,
  102. y: area.y * hRate,
  103. w: area.w * wRate,
  104. h: area.h * hRate,
  105. });
  106. });
  107. this.imgDisplayWidth = clientWidth;
  108. this.imgDisplayHeight = clientHeight;
  109. },
  110. keyEvent(e) {
  111. if (
  112. e.code === "Delete" &&
  113. !e.ctrlKey &&
  114. !e.altKey &&
  115. !e.shiftKey &&
  116. !e.repeat
  117. ) {
  118. if (!this.curArea.id) return;
  119. e.preventDefault();
  120. this.removeArea(this.curArea);
  121. return;
  122. }
  123. },
  124. transformPicConfig(areas) {
  125. const { clientWidth, clientHeight } = this.$refs.imgDom;
  126. this.areas = areas.map((area, index) => {
  127. let narea = {
  128. id: `id-${randomCode()}`,
  129. key: `key-${randomCode()}`,
  130. zIndex: index + 99,
  131. };
  132. if (Object.keys(area).join("") === "i") {
  133. narea = {
  134. ...narea,
  135. x: 0,
  136. y: 0,
  137. w: clientWidth,
  138. h: clientHeight,
  139. };
  140. } else {
  141. narea = {
  142. ...narea,
  143. x: area.x * clientWidth,
  144. y: area.y * clientHeight,
  145. w: area.w * clientWidth,
  146. h: area.h * clientHeight,
  147. };
  148. }
  149. return narea;
  150. });
  151. this.imgDisplayWidth = clientWidth;
  152. this.imgDisplayHeight = clientHeight;
  153. },
  154. addArea(data) {
  155. let area = Object.assign({}, this.initArea, data);
  156. const maxZIndex = this.areas.length
  157. ? this.getMax(this.areas.map((elem) => elem.zIndex))
  158. : 0;
  159. area.id = `id-${randomCode()}`;
  160. area.key = `key-${randomCode()}`;
  161. area.zIndex = maxZIndex + 1;
  162. this.areas.push(area);
  163. this.actCurArea(area);
  164. this.emitChange();
  165. },
  166. modifyArea(area) {
  167. const pos = this.areas.findIndex((elem) => elem.id === area.id);
  168. this.areas.splice(pos, 1, area);
  169. this.actCurArea(area);
  170. this.emitChange();
  171. },
  172. removeArea(area) {
  173. const pos = this.areas.findIndex((elem) => elem.id === area.id);
  174. this.areas.splice(pos, 1);
  175. if (this.areas.length) this.actCurArea(this.areas[0]);
  176. this.emitChange();
  177. },
  178. actCurArea(area) {
  179. this.curArea = area;
  180. this.$emit("curarea-change", area);
  181. },
  182. curareaChange(area) {
  183. if (area.id !== this.curArea.id) this.curArea = {};
  184. },
  185. emitChange() {
  186. const { clientWidth, clientHeight } = this.$refs.imgDom;
  187. const areas = this.areas.map((item) => {
  188. return {
  189. id: item.id,
  190. x: this.toFixed(item.x / clientWidth, 4),
  191. y: this.toFixed(item.y / clientHeight),
  192. w: this.toFixed(item.w / clientWidth, 4),
  193. h: this.toFixed(item.h / clientHeight),
  194. // 是否覆盖整个页面,允许2px的误差
  195. isFull:
  196. item.x <= 2 &&
  197. item.y <= 2 &&
  198. item.w + 2 >= clientWidth &&
  199. item.h + 2 >= clientHeight,
  200. };
  201. });
  202. this.$emit("change", areas);
  203. },
  204. getOffsetInfo(dom, endParentClass = "cropper-areas") {
  205. let parentNode = dom;
  206. let parentNodeClass = parentNode.getAttribute("class") || "";
  207. let offsetTop = 0,
  208. offsetLeft = 0;
  209. while (!parentNodeClass.includes(endParentClass)) {
  210. if (parentNode.offsetParent) {
  211. offsetTop += parentNode.offsetTop;
  212. offsetLeft += parentNode.offsetLeft;
  213. parentNode = parentNode.offsetParent;
  214. } else {
  215. offsetTop += parentNode.clientTop;
  216. offsetLeft += parentNode.clientLeft;
  217. parentNode = parentNode.parentNode;
  218. }
  219. parentNodeClass = parentNode.getAttribute("class") || "";
  220. }
  221. return {
  222. offsetLeft,
  223. offsetTop,
  224. };
  225. },
  226. boxMouseDown($event) {
  227. const { offsetLeft: x, offsetTop: y } = this.getOffsetInfo($event.target);
  228. this.selectionStartPos.x = x + $event.offsetX;
  229. this.selectionStartPos.y = y + $event.offsetY;
  230. },
  231. boxMove($event) {
  232. const { offsetLeft: x, offsetTop: y } = this.getOffsetInfo($event.target);
  233. const selectionEndPos = {
  234. x: x + $event.offsetX,
  235. y: y + $event.offsetY,
  236. };
  237. const sPos = {
  238. x: Math.min(this.selectionStartPos.x, selectionEndPos.x),
  239. y: Math.min(this.selectionStartPos.y, selectionEndPos.y),
  240. };
  241. const ePos = {
  242. x: Math.max(this.selectionStartPos.x, selectionEndPos.x),
  243. y: Math.max(this.selectionStartPos.y, selectionEndPos.y),
  244. };
  245. this.selectionStyles = {
  246. ...sPos,
  247. w: ePos.x - sPos.x,
  248. h: ePos.y - sPos.y,
  249. };
  250. this.IS_SELECT_ACTION = true;
  251. },
  252. boxMoveStop() {
  253. if (
  254. this.IS_SELECT_ACTION &&
  255. this.selectionStyles.w > 20 &&
  256. this.selectionStyles.h > 20
  257. )
  258. this.addArea(this.selectionStyles);
  259. this.selectionStyles = { x: 0, y: 0, w: 0, h: 0 };
  260. this.selectionStartPos = { x: 0, y: 0 };
  261. this.IS_SELECT_ACTION = false;
  262. },
  263. },
  264. beforeDestroy() {
  265. window.removeEventListener("resize", this.windowResizeEvent);
  266. document.removeEventListener("keydown", this.keyEvent);
  267. },
  268. };
  269. </script>