ElementResize.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. <template>
  2. <div
  3. :class="classes"
  4. :style="styles"
  5. v-move-ele.prevent.stop="{
  6. moveStart,
  7. moveElement,
  8. moveStop: moveElementOver,
  9. }"
  10. >
  11. <slot></slot>
  12. <div class="resize-control">
  13. <div
  14. v-for="(control, index) in controlPoints"
  15. :key="index"
  16. :class="control.classes"
  17. v-move-ele.prevent.stop="{
  18. moveElement: control.movePoint,
  19. moveStop: control.movePointOver,
  20. }"
  21. ></div>
  22. <div class="control-line control-line-left"></div>
  23. <div class="control-line control-line-right"></div>
  24. <div class="control-line control-line-top"></div>
  25. <div class="control-line control-line-bottom"></div>
  26. </div>
  27. </div>
  28. </template>
  29. <script>
  30. import MoveEle from "../../directives/move-ele";
  31. export default {
  32. name: "element-resize",
  33. directives: { MoveEle },
  34. props: {
  35. value: {
  36. type: Object,
  37. required: true,
  38. },
  39. active: {
  40. type: Array,
  41. default() {
  42. return ["r", "rb", "b", "lb", "l", "lt", "t", "rt"];
  43. },
  44. },
  45. move: {
  46. type: Boolean,
  47. default: true,
  48. },
  49. minWidth: {
  50. type: Number,
  51. default: 30,
  52. validator(val) {
  53. return val >= 0;
  54. },
  55. },
  56. maxWidth: {
  57. type: Number,
  58. default: 0,
  59. validator(val) {
  60. return val >= 0;
  61. },
  62. },
  63. minHeight: {
  64. type: Number,
  65. default: 30,
  66. validator(val) {
  67. return val >= 0;
  68. },
  69. },
  70. maxHeight: {
  71. type: Number,
  72. default: 0,
  73. validator(val) {
  74. return val >= 0;
  75. },
  76. },
  77. fitParent: {
  78. type: Array,
  79. default() {
  80. return ["w", "h"];
  81. },
  82. },
  83. isCompact: {
  84. type: Boolean,
  85. default: false,
  86. },
  87. transformFit: {
  88. type: Function,
  89. },
  90. elementPk: {
  91. type: String,
  92. default: "",
  93. },
  94. },
  95. data() {
  96. return {
  97. sizePosOrigin: {
  98. x: 0,
  99. y: 0,
  100. w: 0,
  101. h: 0,
  102. },
  103. offsetTopOrigin: 0,
  104. sizePos: {
  105. x: 0,
  106. y: 0,
  107. w: 0,
  108. h: 0,
  109. },
  110. lastSizePos: {},
  111. initOver: false,
  112. controlPoints: [],
  113. positionType: "static",
  114. parentNodeSize: {
  115. w: 0,
  116. h: 0,
  117. },
  118. };
  119. },
  120. computed: {
  121. styles() {
  122. return this.initOver
  123. ? {
  124. left: this.sizePos.x + "px",
  125. top: this.sizePos.y + "px",
  126. width: this.sizePos.w + "px",
  127. height: this.sizePos.h + "px",
  128. zIndex: this.sizePos.zindex,
  129. position: this.positionType,
  130. }
  131. : {};
  132. },
  133. classes() {
  134. return [
  135. "element-resize",
  136. {
  137. "element-resize-move": this.move,
  138. "element-resize-init": this.initOver,
  139. "element-resize-compact": this.isCompact,
  140. },
  141. ];
  142. },
  143. fitParentTypeWidth() {
  144. return this.fitParent.includes("w");
  145. },
  146. fitParentTypeHeight() {
  147. return this.fitParent.includes("h");
  148. },
  149. },
  150. created() {
  151. this.initControlPoints();
  152. },
  153. mounted() {
  154. this.initSize();
  155. },
  156. methods: {
  157. initControlPoints() {
  158. const posName = {
  159. l: "Left",
  160. r: "Right",
  161. t: "Top",
  162. b: "Bottom",
  163. };
  164. this.controlPoints = this.active.map((type) => {
  165. const posFullName = type
  166. .split("")
  167. .map((item) => {
  168. return posName[item];
  169. })
  170. .join("");
  171. return {
  172. classes: ["control-point", `control-point-${type}`],
  173. movePoint: this[`move${posFullName}Point`],
  174. movePointOver: this.moveOver,
  175. };
  176. });
  177. },
  178. initSize() {
  179. const resizeDom = this.$el.childNodes[0];
  180. this.positionType = window.getComputedStyle(resizeDom).position;
  181. this.sizePos = { ...this.value };
  182. this.lastSizePos = { ...this.value };
  183. this.sizePosOrigin = { ...this.value };
  184. if (this.positionType === "relative")
  185. this.offsetTopOrigin = this.$el.offsetTop;
  186. this.initOver = true;
  187. },
  188. fetchValidSizePos(sizePos, actionType) {
  189. if (sizePos.w <= this.minWidth) {
  190. sizePos.w = this.minWidth;
  191. }
  192. if (this.maxWidth !== 0 && sizePos.w >= this.maxWidth) {
  193. sizePos.w = this.maxWidth;
  194. }
  195. if (sizePos.h <= this.minHeight) {
  196. sizePos.h = this.minHeight;
  197. }
  198. if (this.maxHeight !== 0 && sizePos.h >= this.maxHeight) {
  199. sizePos.h = this.maxHeight;
  200. }
  201. if (!this.fitParent.length) {
  202. this.lastSizePos = { ...sizePos };
  203. return sizePos;
  204. }
  205. // 不同的定位方式,计算方式有差异
  206. this.parentNodeSize = {
  207. w: this.$el.offsetParent.offsetWidth,
  208. h: this.$el.offsetParent.offsetHeight,
  209. };
  210. if (this.fitParentTypeWidth) {
  211. if (sizePos.x <= 0) {
  212. sizePos.x = 0;
  213. if (actionType.includes("left")) sizePos.w = this.lastSizePos.w;
  214. }
  215. if (sizePos.x + sizePos.w > this.parentNodeSize.w) {
  216. sizePos.x = this.lastSizePos.x;
  217. sizePos.w = this.parentNodeSize.w - sizePos.x;
  218. }
  219. }
  220. if (this.fitParentTypeHeight) {
  221. if (this.positionType === "relative") {
  222. const elOffsetTop = this.$el.offsetTop;
  223. if (this.sizePosOrigin.y - sizePos.y >= this.offsetTopOrigin) {
  224. sizePos.h = this.lastSizePos.h;
  225. sizePos.y = this.sizePosOrigin.y - this.offsetTopOrigin;
  226. }
  227. if (elOffsetTop + sizePos.h >= this.parentNodeSize.h) {
  228. sizePos.y = this.lastSizePos.y;
  229. sizePos.h = this.lastSizePos.h;
  230. // TODO:这里如果拖动太快,会有问题
  231. // console.log(this.parentNodeSize.h, elOffsetTop, sizePos.h);
  232. }
  233. } else {
  234. if (sizePos.y <= 0) {
  235. sizePos.y = 0;
  236. if (actionType.includes("top")) sizePos.h = this.lastSizePos.h;
  237. }
  238. if (sizePos.y + sizePos.h > this.parentNodeSize.h) {
  239. sizePos.y = this.lastSizePos.y;
  240. sizePos.h = this.parentNodeSize.h - sizePos.y;
  241. }
  242. }
  243. }
  244. this.lastSizePos = { ...sizePos };
  245. return this.transformFit
  246. ? Object.assign(
  247. {},
  248. sizePos,
  249. this.transformFit({ id: this.elementPk, ...sizePos }, actionType)
  250. )
  251. : sizePos;
  252. },
  253. getLeftSize(left) {
  254. return {
  255. w: -left + this.sizePosOrigin.w,
  256. x: left + this.sizePosOrigin.x,
  257. };
  258. },
  259. getRightSize(left) {
  260. return {
  261. w: left + this.sizePosOrigin.w,
  262. };
  263. },
  264. getTopSize(top) {
  265. return {
  266. h: -top + this.sizePosOrigin.h,
  267. y: top + this.sizePosOrigin.y,
  268. };
  269. },
  270. getBottomSize(top) {
  271. return {
  272. h: top + this.sizePosOrigin.h,
  273. };
  274. },
  275. moveLeftPoint({ left }) {
  276. const sp = { ...this.sizePos, ...this.getLeftSize(left) };
  277. this.sizePos = { ...this.fetchValidSizePos(sp, "left") };
  278. this.emitChange();
  279. },
  280. moveRightPoint({ left }) {
  281. const sp = { ...this.sizePos, ...this.getRightSize(left) };
  282. this.sizePos = { ...this.fetchValidSizePos(sp, "right") };
  283. this.emitChange();
  284. },
  285. moveTopPoint({ top }) {
  286. const sp = { ...this.sizePos, ...this.getTopSize(top) };
  287. this.sizePos = { ...this.fetchValidSizePos(sp, "top") };
  288. this.emitChange();
  289. },
  290. moveBottomPoint({ top }) {
  291. const sp = { ...this.sizePos, ...this.getBottomSize(top) };
  292. this.sizePos = { ...this.fetchValidSizePos(sp, "bottom") };
  293. this.emitChange();
  294. },
  295. moveLeftTopPoint({ left, top }) {
  296. const sp = {
  297. ...this.sizePos,
  298. ...this.getLeftSize(left),
  299. ...this.getTopSize(top),
  300. };
  301. this.sizePos = { ...this.fetchValidSizePos(sp, "left-top") };
  302. this.emitChange();
  303. },
  304. moveRightTopPoint({ left, top }) {
  305. const sp = {
  306. ...this.sizePos,
  307. ...this.getRightSize(left),
  308. ...this.getTopSize(top),
  309. };
  310. this.sizePos = { ...this.fetchValidSizePos(sp, "right-top") };
  311. this.emitChange();
  312. },
  313. moveLeftBottomPoint({ left, top }) {
  314. const sp = {
  315. ...this.sizePos,
  316. ...this.getLeftSize(left),
  317. ...this.getBottomSize(top),
  318. };
  319. this.sizePos = { ...this.fetchValidSizePos(sp, "left-bottom") };
  320. this.emitChange();
  321. },
  322. moveRightBottomPoint({ left, top }) {
  323. const sp = {
  324. ...this.sizePos,
  325. ...this.getRightSize(left),
  326. ...this.getBottomSize(top),
  327. };
  328. this.sizePos = { ...this.fetchValidSizePos(sp, "right-bottom") };
  329. this.emitChange();
  330. },
  331. moveOver() {
  332. this.sizePosOrigin = { ...this.sizePos };
  333. this.lastSizePos = { ...this.sizePos };
  334. if (this.positionType === "relative")
  335. this.offsetTopOrigin = this.$el.offsetTop < 0 ? 0 : this.$el.offsetTop;
  336. this.emitChange();
  337. this.$emit("resize-over", this.sizePos);
  338. },
  339. moveStart() {
  340. this.$emit("on-click");
  341. },
  342. moveElement({ left, top }) {
  343. if (!this.move) return;
  344. const sp = {
  345. ...this.sizePos,
  346. ...{
  347. x: left + this.sizePosOrigin.x,
  348. y: top + this.sizePosOrigin.y,
  349. },
  350. };
  351. this.sizePos = { ...this.fetchValidSizePos(sp, "move") };
  352. this.emitChange();
  353. },
  354. moveElementOver() {
  355. if (!this.move) return;
  356. this.moveOver();
  357. },
  358. emitChange() {
  359. this.$emit("input", this.sizePos);
  360. this.$emit("change", this.sizePos);
  361. },
  362. },
  363. };
  364. </script>
  365. <style lang="scss" scope>
  366. .element-resize {
  367. position: static;
  368. z-index: auto;
  369. background: #fff;
  370. box-sizing: content-box;
  371. &-move {
  372. cursor: move;
  373. }
  374. &-init {
  375. > div:first-child {
  376. width: 100% !important;
  377. height: 100% !important;
  378. position: relative !important;
  379. top: 0 !important;
  380. left: 0 !important;
  381. overflow: hidden;
  382. }
  383. }
  384. .control-point {
  385. position: absolute;
  386. width: 8px;
  387. height: 8px;
  388. border-radius: 50%;
  389. background: #617bea;
  390. z-index: 99;
  391. &-l {
  392. left: 0;
  393. top: 50%;
  394. width: 5px;
  395. height: 20px;
  396. margin-top: -10px;
  397. margin-left: -3px;
  398. border-radius: 0;
  399. padding-top: 3px;
  400. cursor: w-resize;
  401. text-align: center;
  402. color: #fff;
  403. &::before {
  404. content: ".";
  405. display: block;
  406. font-size: 16px;
  407. line-height: 1;
  408. margin-top: -9px;
  409. }
  410. &::after {
  411. content: ".";
  412. display: block;
  413. font-size: 16px;
  414. line-height: 1;
  415. margin-top: -9px;
  416. }
  417. }
  418. &-lt {
  419. left: 0;
  420. top: 0;
  421. margin-top: -5px;
  422. margin-left: -5px;
  423. cursor: nw-resize;
  424. }
  425. &-lb {
  426. left: 0;
  427. bottom: 0;
  428. margin-bottom: -5px;
  429. margin-left: -5px;
  430. cursor: sw-resize;
  431. }
  432. &-r {
  433. right: 0;
  434. top: 50%;
  435. width: 5px;
  436. height: 20px;
  437. margin-top: -10px;
  438. margin-right: -3px;
  439. cursor: e-resize;
  440. border-radius: 0;
  441. padding-top: 3px;
  442. text-align: center;
  443. color: #fff;
  444. &::before {
  445. content: ".";
  446. display: block;
  447. font-size: 16px;
  448. line-height: 1;
  449. margin-top: -9px;
  450. }
  451. &::after {
  452. content: ".";
  453. display: block;
  454. font-size: 16px;
  455. line-height: 1;
  456. margin-top: -9px;
  457. }
  458. }
  459. &-rt {
  460. right: 0;
  461. top: 0;
  462. margin-top: -5px;
  463. margin-right: -5px;
  464. cursor: ne-resize;
  465. }
  466. &-rb {
  467. right: 0;
  468. bottom: 0;
  469. margin-bottom: -5px;
  470. margin-right: -5px;
  471. cursor: se-resize;
  472. }
  473. &-t {
  474. left: 50%;
  475. top: 0;
  476. width: 30px;
  477. height: 5px;
  478. border-radius: 0;
  479. margin-top: -3px;
  480. margin-left: -15px;
  481. cursor: n-resize;
  482. text-align: center;
  483. color: #fff;
  484. &::before {
  485. content: "...";
  486. display: inline-block;
  487. vertical-align: top;
  488. font-size: 16px;
  489. line-height: 1;
  490. margin-top: -10px;
  491. }
  492. }
  493. &-b {
  494. left: 50%;
  495. bottom: 0;
  496. width: 30px;
  497. height: 5px;
  498. border-radius: 0;
  499. margin-bottom: -3px;
  500. margin-left: -15px;
  501. cursor: s-resize;
  502. text-align: center;
  503. color: #fff;
  504. &::before {
  505. content: "...";
  506. display: inline-block;
  507. vertical-align: top;
  508. font-size: 16px;
  509. line-height: 1;
  510. margin-top: -10px;
  511. }
  512. }
  513. }
  514. .control-line {
  515. position: absolute;
  516. z-index: 98;
  517. &-left {
  518. height: 100%;
  519. left: -1px;
  520. top: 0;
  521. border-left: 1px solid #617bea;
  522. }
  523. &-right {
  524. height: 100%;
  525. right: -1px;
  526. top: 0;
  527. border-left: 1px solid #617bea;
  528. }
  529. &-top {
  530. width: 100%;
  531. left: 0;
  532. top: -1px;
  533. border-top: 1px solid #617bea;
  534. }
  535. &-bottom {
  536. width: 100%;
  537. left: 0;
  538. bottom: -1px;
  539. border-top: 1px solid #617bea;
  540. }
  541. }
  542. &-compact {
  543. .control-line {
  544. &-left {
  545. left: 0;
  546. border-left: 1px dashed #bbb;
  547. }
  548. &-right {
  549. right: 0;
  550. border-left: 1px dashed #bbb;
  551. }
  552. &-top {
  553. top: 0;
  554. border-top: 1px dashed #bbb;
  555. }
  556. &-bottom {
  557. bottom: 0;
  558. border-top: 1px dashed #bbb;
  559. }
  560. }
  561. }
  562. }
  563. </style>