ElementResize.vue 13 KB

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