ElementResize.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  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 actions = {
  159. l: this.moveLeftPoint,
  160. r: this.moveRightPoint,
  161. t: this.moveTopPoint,
  162. b: this.moveBottomPoint,
  163. lt: this.moveLeftTopPoint,
  164. rt: this.moveRightTopPoint,
  165. lb: this.moveLeftBottomPoint,
  166. rb: this.moveRightBottomPoint,
  167. };
  168. this.controlPoints = this.active.map((type) => {
  169. return {
  170. classes: ["control-point", `control-point-${type}`],
  171. movePoint: actions[type],
  172. movePointOver: (data) => {
  173. actions[type](data);
  174. this.movePointOver();
  175. },
  176. };
  177. });
  178. },
  179. initSize() {
  180. const resizeDom = this.$el.childNodes[0];
  181. this.positionType = window.getComputedStyle(resizeDom).position;
  182. this.sizePos = { ...this.value };
  183. this.lastSizePos = { ...this.value };
  184. this.sizePosOrigin = { ...this.value };
  185. if (this.positionType === "relative")
  186. this.offsetTopOrigin = this.$el.offsetTop;
  187. this.initOver = true;
  188. },
  189. fetchValidSizePos(sizePos, actionType) {
  190. if (sizePos.w <= this.minWidth) {
  191. sizePos.w = this.minWidth;
  192. if (actionType.includes("l"))
  193. sizePos.x = this.lastSizePos.x + this.lastSizePos.w - sizePos.w;
  194. }
  195. if (this.maxWidth !== 0 && sizePos.w >= this.maxWidth) {
  196. sizePos.w = this.maxWidth;
  197. }
  198. if (sizePos.h <= this.minHeight) {
  199. sizePos.h = this.minHeight;
  200. if (actionType.includes("t"))
  201. sizePos.y = this.lastSizePos.y + this.lastSizePos.h - sizePos.h;
  202. }
  203. if (this.maxHeight !== 0 && sizePos.h >= this.maxHeight) {
  204. sizePos.h = this.maxHeight;
  205. }
  206. if (!this.fitParent.length) {
  207. this.lastSizePos = { ...sizePos };
  208. return sizePos;
  209. }
  210. // 不同的定位方式,计算方式有差异
  211. this.parentNodeSize = {
  212. w: this.$el.offsetParent.offsetWidth,
  213. h: this.$el.offsetParent.offsetHeight,
  214. };
  215. if (this.fitParentTypeWidth) {
  216. if (sizePos.x <= 0) {
  217. sizePos.x = 0;
  218. if (actionType.includes("l")) sizePos.w = this.lastSizePos.w;
  219. }
  220. if (sizePos.x + sizePos.w >= this.parentNodeSize.w) {
  221. if (actionType === "move") {
  222. sizePos.x = this.parentNodeSize.w - sizePos.w;
  223. } else {
  224. sizePos.w = this.parentNodeSize.w - sizePos.x;
  225. }
  226. }
  227. }
  228. if (this.fitParentTypeHeight) {
  229. if (this.positionType === "relative") {
  230. const elOffsetTop = this.$el.offsetTop;
  231. // relative目前只开放bottom编辑,这里也只处理这中场景
  232. if (elOffsetTop + sizePos.h >= this.parentNodeSize.h) {
  233. sizePos.h = this.parentNodeSize.h - elOffsetTop;
  234. }
  235. } else {
  236. if (sizePos.y <= 0) {
  237. sizePos.y = 0;
  238. if (actionType.includes("t")) {
  239. sizePos.h = this.lastSizePos.h + this.lastSizePos.y;
  240. }
  241. }
  242. if (sizePos.y + sizePos.h > this.parentNodeSize.h) {
  243. if (actionType === "move") {
  244. sizePos.y = this.parentNodeSize.h - sizePos.h;
  245. } else {
  246. sizePos.h = this.parentNodeSize.h - sizePos.y;
  247. }
  248. }
  249. }
  250. }
  251. this.lastSizePos = { ...sizePos };
  252. return this.transformFit
  253. ? Object.assign(
  254. {},
  255. sizePos,
  256. this.transformFit({ id: this.elementPk, ...sizePos }, actionType)
  257. )
  258. : sizePos;
  259. },
  260. getLeftSize(left) {
  261. return {
  262. w: -left + this.sizePosOrigin.w,
  263. x: left + this.sizePosOrigin.x,
  264. };
  265. },
  266. getRightSize(left) {
  267. return {
  268. w: left + this.sizePosOrigin.w,
  269. };
  270. },
  271. getTopSize(top) {
  272. return {
  273. h: -top + this.sizePosOrigin.h,
  274. y: top + this.sizePosOrigin.y,
  275. };
  276. },
  277. getBottomSize(top) {
  278. return {
  279. h: top + this.sizePosOrigin.h,
  280. };
  281. },
  282. moveLeftPoint({ left }) {
  283. const sp = { ...this.sizePos, ...this.getLeftSize(left) };
  284. this.sizePos = { ...this.fetchValidSizePos(sp, "l") };
  285. this.emitChange();
  286. },
  287. moveRightPoint({ left }) {
  288. const sp = { ...this.sizePos, ...this.getRightSize(left) };
  289. this.sizePos = { ...this.fetchValidSizePos(sp, "r") };
  290. this.emitChange();
  291. },
  292. moveTopPoint({ top }) {
  293. const sp = { ...this.sizePos, ...this.getTopSize(top) };
  294. this.sizePos = { ...this.fetchValidSizePos(sp, "t") };
  295. this.emitChange();
  296. },
  297. moveBottomPoint({ top }) {
  298. const sp = { ...this.sizePos, ...this.getBottomSize(top) };
  299. this.sizePos = { ...this.fetchValidSizePos(sp, "b") };
  300. this.emitChange();
  301. },
  302. moveLeftTopPoint({ left, top }) {
  303. const sp = {
  304. ...this.sizePos,
  305. ...this.getLeftSize(left),
  306. ...this.getTopSize(top),
  307. };
  308. this.sizePos = { ...this.fetchValidSizePos(sp, "lt") };
  309. this.emitChange();
  310. },
  311. moveRightTopPoint({ left, top }) {
  312. const sp = {
  313. ...this.sizePos,
  314. ...this.getRightSize(left),
  315. ...this.getTopSize(top),
  316. };
  317. this.sizePos = { ...this.fetchValidSizePos(sp, "rt") };
  318. this.emitChange();
  319. },
  320. moveLeftBottomPoint({ left, top }) {
  321. const sp = {
  322. ...this.sizePos,
  323. ...this.getLeftSize(left),
  324. ...this.getBottomSize(top),
  325. };
  326. this.sizePos = { ...this.fetchValidSizePos(sp, "lb") };
  327. this.emitChange();
  328. },
  329. moveRightBottomPoint({ left, top }) {
  330. const sp = {
  331. ...this.sizePos,
  332. ...this.getRightSize(left),
  333. ...this.getBottomSize(top),
  334. };
  335. this.sizePos = { ...this.fetchValidSizePos(sp, "rb") };
  336. this.emitChange();
  337. },
  338. movePointOver() {
  339. this.sizePosOrigin = { ...this.sizePos };
  340. this.lastSizePos = { ...this.sizePos };
  341. if (this.positionType === "relative")
  342. this.offsetTopOrigin = this.$el.offsetTop < 0 ? 0 : this.$el.offsetTop;
  343. this.$emit("resize-over", this.sizePos);
  344. },
  345. moveStart() {
  346. this.$emit("on-click");
  347. },
  348. moveElement({ left, top }) {
  349. if (!this.move) return;
  350. const sp = {
  351. ...this.sizePos,
  352. ...{
  353. x: left + this.sizePosOrigin.x,
  354. y: top + this.sizePosOrigin.y,
  355. },
  356. };
  357. this.sizePos = { ...this.fetchValidSizePos(sp, "move") };
  358. this.emitChange();
  359. },
  360. moveElementOver({ left, top }) {
  361. if (!this.move) return;
  362. this.moveElement({ left, top });
  363. this.movePointOver();
  364. },
  365. emitChange() {
  366. this.$emit("input", this.sizePos);
  367. this.$emit("change", this.sizePos);
  368. },
  369. },
  370. };
  371. </script>
  372. <style lang="scss" scope>
  373. .element-resize {
  374. position: static;
  375. z-index: auto;
  376. background: #fff;
  377. box-sizing: content-box;
  378. &-move {
  379. cursor: move;
  380. }
  381. &-init {
  382. > div:first-child {
  383. width: 100% !important;
  384. height: 100% !important;
  385. position: relative !important;
  386. top: 0 !important;
  387. left: 0 !important;
  388. overflow: hidden;
  389. }
  390. }
  391. .control-point {
  392. position: absolute;
  393. width: 8px;
  394. height: 8px;
  395. border-radius: 50%;
  396. background: #617bea;
  397. z-index: 99;
  398. &-l {
  399. left: 0;
  400. top: 50%;
  401. width: 5px;
  402. height: 20px;
  403. margin-top: -10px;
  404. margin-left: -3px;
  405. border-radius: 0;
  406. padding-top: 3px;
  407. cursor: w-resize;
  408. text-align: center;
  409. color: #fff;
  410. &::before {
  411. content: ".";
  412. display: block;
  413. font-size: 16px;
  414. line-height: 1;
  415. margin-top: -9px;
  416. }
  417. &::after {
  418. content: ".";
  419. display: block;
  420. font-size: 16px;
  421. line-height: 1;
  422. margin-top: -9px;
  423. }
  424. }
  425. &-lt {
  426. left: 0;
  427. top: 0;
  428. margin-top: -5px;
  429. margin-left: -5px;
  430. cursor: nw-resize;
  431. }
  432. &-lb {
  433. left: 0;
  434. bottom: 0;
  435. margin-bottom: -5px;
  436. margin-left: -5px;
  437. cursor: sw-resize;
  438. }
  439. &-r {
  440. right: 0;
  441. top: 50%;
  442. width: 5px;
  443. height: 20px;
  444. margin-top: -10px;
  445. margin-right: -3px;
  446. cursor: e-resize;
  447. border-radius: 0;
  448. padding-top: 3px;
  449. text-align: center;
  450. color: #fff;
  451. &::before {
  452. content: ".";
  453. display: block;
  454. font-size: 16px;
  455. line-height: 1;
  456. margin-top: -9px;
  457. }
  458. &::after {
  459. content: ".";
  460. display: block;
  461. font-size: 16px;
  462. line-height: 1;
  463. margin-top: -9px;
  464. }
  465. }
  466. &-rt {
  467. right: 0;
  468. top: 0;
  469. margin-top: -5px;
  470. margin-right: -5px;
  471. cursor: ne-resize;
  472. }
  473. &-rb {
  474. right: 0;
  475. bottom: 0;
  476. margin-bottom: -5px;
  477. margin-right: -5px;
  478. cursor: se-resize;
  479. }
  480. &-t {
  481. left: 50%;
  482. top: 0;
  483. width: 30px;
  484. height: 5px;
  485. border-radius: 0;
  486. margin-top: -3px;
  487. margin-left: -15px;
  488. cursor: n-resize;
  489. text-align: center;
  490. color: #fff;
  491. &::before {
  492. content: "...";
  493. display: inline-block;
  494. vertical-align: top;
  495. font-size: 16px;
  496. line-height: 1;
  497. margin-top: -10px;
  498. }
  499. }
  500. &-b {
  501. left: 50%;
  502. bottom: 0;
  503. width: 30px;
  504. height: 5px;
  505. border-radius: 0;
  506. margin-bottom: -3px;
  507. margin-left: -15px;
  508. cursor: s-resize;
  509. text-align: center;
  510. color: #fff;
  511. &::before {
  512. content: "...";
  513. display: inline-block;
  514. vertical-align: top;
  515. font-size: 16px;
  516. line-height: 1;
  517. margin-top: -10px;
  518. }
  519. }
  520. }
  521. .control-line {
  522. position: absolute;
  523. z-index: 98;
  524. &-left {
  525. height: 100%;
  526. left: -1px;
  527. top: 0;
  528. border-left: 1px solid #617bea;
  529. }
  530. &-right {
  531. height: 100%;
  532. right: -1px;
  533. top: 0;
  534. border-left: 1px solid #617bea;
  535. }
  536. &-top {
  537. width: 100%;
  538. left: 0;
  539. top: -1px;
  540. border-top: 1px solid #617bea;
  541. }
  542. &-bottom {
  543. width: 100%;
  544. left: 0;
  545. bottom: -1px;
  546. border-top: 1px solid #617bea;
  547. }
  548. }
  549. &-compact {
  550. .control-line {
  551. &-left {
  552. left: 0;
  553. border-left: 1px dashed #bbb;
  554. }
  555. &-right {
  556. right: 0;
  557. border-left: 1px dashed #bbb;
  558. }
  559. &-top {
  560. top: 0;
  561. border-top: 1px dashed #bbb;
  562. }
  563. &-bottom {
  564. bottom: 0;
  565. border-top: 1px dashed #bbb;
  566. }
  567. }
  568. }
  569. }
  570. </style>